1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.notification.row;
18 
19 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
20 import static android.app.NotificationManager.IMPORTANCE_LOW;
21 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
22 
23 import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
24 
25 import static java.lang.annotation.RetentionPolicy.SOURCE;
26 
27 import android.annotation.IntDef;
28 import android.annotation.Nullable;
29 import android.app.INotificationManager;
30 import android.app.Notification;
31 import android.app.NotificationChannel;
32 import android.app.NotificationChannelGroup;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.pm.ActivityInfo;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.graphics.drawable.Drawable;
40 import android.metrics.LogMaker;
41 import android.os.Handler;
42 import android.os.RemoteException;
43 import android.service.notification.StatusBarNotification;
44 import android.text.Html;
45 import android.text.TextUtils;
46 import android.transition.ChangeBounds;
47 import android.transition.Fade;
48 import android.transition.TransitionManager;
49 import android.transition.TransitionSet;
50 import android.util.AttributeSet;
51 import android.util.Log;
52 import android.view.View;
53 import android.view.accessibility.AccessibilityEvent;
54 import android.widget.ImageView;
55 import android.widget.LinearLayout;
56 import android.widget.TextView;
57 
58 import com.android.internal.annotations.VisibleForTesting;
59 import com.android.internal.logging.MetricsLogger;
60 import com.android.internal.logging.UiEventLogger;
61 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
62 import com.android.systemui.Dependency;
63 import com.android.systemui.R;
64 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
65 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
66 
67 import java.lang.annotation.Retention;
68 import java.util.List;
69 import java.util.Set;
70 
71 /**
72  * The guts of a notification revealed when performing a long press.
73  */
74 public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent {
75     private static final String TAG = "InfoGuts";
76     private int mActualHeight;
77 
78     @IntDef(prefix = { "ACTION_" }, value = {
79             ACTION_NONE,
80             ACTION_TOGGLE_ALERT,
81             ACTION_TOGGLE_SILENT,
82     })
83     public @interface NotificationInfoAction {
84     }
85 
86     public static final int ACTION_NONE = 0;
87     // standard controls
88     static final int ACTION_TOGGLE_SILENT = 2;
89     // standard controls
90     private static final int ACTION_TOGGLE_ALERT = 5;
91 
92     private TextView mPriorityDescriptionView;
93     private TextView mSilentDescriptionView;
94     private TextView mAutomaticDescriptionView;
95 
96     private INotificationManager mINotificationManager;
97     private OnUserInteractionCallback mOnUserInteractionCallback;
98     private PackageManager mPm;
99     private MetricsLogger mMetricsLogger;
100     private ChannelEditorDialogController mChannelEditorDialogController;
101     private AssistantFeedbackController mAssistantFeedbackController;
102 
103     private String mPackageName;
104     private String mAppName;
105     private int mAppUid;
106     private String mDelegatePkg;
107     private int mNumUniqueChannelsInRow;
108     private Set<NotificationChannel> mUniqueChannelsInRow;
109     private NotificationChannel mSingleNotificationChannel;
110     private int mStartingChannelImportance;
111     private boolean mWasShownHighPriority;
112     private boolean mPressedApply;
113     private boolean mPresentingChannelEditorDialog = false;
114     private boolean mShowAutomaticSetting;
115 
116     /**
117      * The last importance level chosen by the user.  Null if the user has not chosen an importance
118      * level; non-null once the user takes an action which indicates an explicit preference.
119      */
120     @Nullable private Integer mChosenImportance;
121     private boolean mIsAutomaticChosen;
122     private boolean mIsSingleDefaultChannel;
123     private boolean mIsNonblockable;
124     private NotificationEntry mEntry;
125     private StatusBarNotification mSbn;
126     private boolean mIsDeviceProvisioned;
127 
128     private OnSettingsClickListener mOnSettingsClickListener;
129     private OnAppSettingsClickListener mAppSettingsClickListener;
130     private NotificationGuts mGutsContainer;
131     private Drawable mPkgIcon;
132     private UiEventLogger mUiEventLogger;
133 
134     @VisibleForTesting
135     boolean mSkipPost = false;
136 
137     // used by standard ui
138     private OnClickListener mOnAutomatic = v -> {
139         mIsAutomaticChosen = true;
140         applyAlertingBehavior(BEHAVIOR_AUTOMATIC, true /* userTriggered */);
141     };
142 
143     // used by standard ui
144     private OnClickListener mOnAlert = v -> {
145         mChosenImportance = IMPORTANCE_DEFAULT;
146         mIsAutomaticChosen = false;
147         applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */);
148     };
149 
150     // used by standard ui
151     private OnClickListener mOnSilent = v -> {
152         mChosenImportance = IMPORTANCE_LOW;
153         mIsAutomaticChosen = false;
154         applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */);
155     };
156 
157     // used by standard ui
158     private OnClickListener mOnDismissSettings = v -> {
159         mPressedApply = true;
160         mGutsContainer.closeControls(v, true);
161     };
162 
NotificationInfo(Context context, AttributeSet attrs)163     public NotificationInfo(Context context, AttributeSet attrs) {
164         super(context, attrs);
165     }
166 
167     @Override
onFinishInflate()168     protected void onFinishInflate() {
169         super.onFinishInflate();
170 
171         mPriorityDescriptionView = findViewById(R.id.alert_summary);
172         mSilentDescriptionView = findViewById(R.id.silence_summary);
173         mAutomaticDescriptionView = findViewById(R.id.automatic_summary);
174     }
175 
176     // Specify a CheckSaveListener to override when/if the user's changes are committed.
177     public interface CheckSaveListener {
178         // Invoked when importance has changed and the NotificationInfo wants to try to save it.
179         // Listener should run saveImportance unless the change should be canceled.
checkSave(Runnable saveImportance, StatusBarNotification sbn)180         void checkSave(Runnable saveImportance, StatusBarNotification sbn);
181     }
182 
183     public interface OnSettingsClickListener {
onClick(View v, NotificationChannel channel, int appUid)184         void onClick(View v, NotificationChannel channel, int appUid);
185     }
186 
187     public interface OnAppSettingsClickListener {
onClick(View v, Intent intent)188         void onClick(View v, Intent intent);
189     }
190 
bindNotification( PackageManager pm, INotificationManager iNotificationManager, OnUserInteractionCallback onUserInteractionCallback, ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, Set<NotificationChannel> uniqueChannelsInRow, NotificationEntry entry, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, UiEventLogger uiEventLogger, boolean isDeviceProvisioned, boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController)191     public void bindNotification(
192             PackageManager pm,
193             INotificationManager iNotificationManager,
194             OnUserInteractionCallback onUserInteractionCallback,
195             ChannelEditorDialogController channelEditorDialogController,
196             String pkg,
197             NotificationChannel notificationChannel,
198             Set<NotificationChannel> uniqueChannelsInRow,
199             NotificationEntry entry,
200             OnSettingsClickListener onSettingsClick,
201             OnAppSettingsClickListener onAppSettingsClick,
202             UiEventLogger uiEventLogger,
203             boolean isDeviceProvisioned,
204             boolean isNonblockable,
205             boolean wasShownHighPriority,
206             AssistantFeedbackController assistantFeedbackController)
207             throws RemoteException {
208         mINotificationManager = iNotificationManager;
209         mMetricsLogger = Dependency.get(MetricsLogger.class);
210         mOnUserInteractionCallback = onUserInteractionCallback;
211         mChannelEditorDialogController = channelEditorDialogController;
212         mAssistantFeedbackController = assistantFeedbackController;
213         mPackageName = pkg;
214         mUniqueChannelsInRow = uniqueChannelsInRow;
215         mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
216         mEntry = entry;
217         mSbn = entry.getSbn();
218         mPm = pm;
219         mAppSettingsClickListener = onAppSettingsClick;
220         mAppName = mPackageName;
221         mOnSettingsClickListener = onSettingsClick;
222         mSingleNotificationChannel = notificationChannel;
223         mStartingChannelImportance = mSingleNotificationChannel.getImportance();
224         mWasShownHighPriority = wasShownHighPriority;
225         mIsNonblockable = isNonblockable;
226         mAppUid = mSbn.getUid();
227         mDelegatePkg = mSbn.getOpPkg();
228         mIsDeviceProvisioned = isDeviceProvisioned;
229         mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled();
230         mUiEventLogger = uiEventLogger;
231 
232         int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
233                 pkg, mAppUid, false /* includeDeleted */);
234         if (mNumUniqueChannelsInRow == 0) {
235             throw new IllegalArgumentException("bindNotification requires at least one channel");
236         } else  {
237             // Special behavior for the Default channel if no other channels have been defined.
238             mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1
239                     && mSingleNotificationChannel.getId().equals(
240                             NotificationChannel.DEFAULT_CHANNEL_ID)
241                     && numTotalChannels == 1;
242         }
243         mIsAutomaticChosen = getAlertingBehavior() == BEHAVIOR_AUTOMATIC;
244 
245         bindHeader();
246         bindChannelDetails();
247 
248         bindInlineControls();
249 
250         logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN);
251         mMetricsLogger.write(notificationControlsLogMaker());
252     }
253 
bindInlineControls()254     private void bindInlineControls() {
255         if (mIsNonblockable) {
256             findViewById(R.id.non_configurable_text).setVisibility(VISIBLE);
257             findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
258             findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
259             ((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button);
260             findViewById(R.id.turn_off_notifications).setVisibility(GONE);
261         } else if (mNumUniqueChannelsInRow > 1) {
262             findViewById(R.id.non_configurable_text).setVisibility(GONE);
263             findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
264             findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE);
265         } else {
266             findViewById(R.id.non_configurable_text).setVisibility(GONE);
267             findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
268             findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
269         }
270 
271         View turnOffButton = findViewById(R.id.turn_off_notifications);
272         turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener());
273         turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
274                 ? VISIBLE : GONE);
275 
276         View done = findViewById(R.id.done);
277         done.setOnClickListener(mOnDismissSettings);
278         done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
279 
280         View silent = findViewById(R.id.silence);
281         View alert = findViewById(R.id.alert);
282         silent.setOnClickListener(mOnSilent);
283         alert.setOnClickListener(mOnAlert);
284 
285         View automatic = findViewById(R.id.automatic);
286         if (mShowAutomaticSetting) {
287             mAutomaticDescriptionView.setText(Html.fromHtml(mContext.getText(
288                     mAssistantFeedbackController.getInlineDescriptionResource(mEntry)).toString()));
289             automatic.setVisibility(VISIBLE);
290             automatic.setOnClickListener(mOnAutomatic);
291         } else {
292             automatic.setVisibility(GONE);
293         }
294 
295         int behavior = getAlertingBehavior();
296         applyAlertingBehavior(behavior, false /* userTriggered */);
297     }
298 
bindHeader()299     private void bindHeader() {
300         // Package name
301         mPkgIcon = null;
302         ApplicationInfo info;
303         try {
304             info = mPm.getApplicationInfo(
305                     mPackageName,
306                     PackageManager.MATCH_UNINSTALLED_PACKAGES
307                             | PackageManager.MATCH_DISABLED_COMPONENTS
308                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
309                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
310             if (info != null) {
311                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
312                 mPkgIcon = mPm.getApplicationIcon(info);
313             }
314         } catch (PackageManager.NameNotFoundException e) {
315             // app is gone, just show package name and generic icon
316             mPkgIcon = mPm.getDefaultActivityIcon();
317         }
318         ((ImageView) findViewById(R.id.pkg_icon)).setImageDrawable(mPkgIcon);
319         ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
320 
321         // Delegate
322         bindDelegate();
323 
324         // Set up app settings link (i.e. Customize)
325         View settingsLinkView = findViewById(R.id.app_settings);
326         Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
327                 mSingleNotificationChannel,
328                 mSbn.getId(), mSbn.getTag());
329         if (settingsIntent != null
330                 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
331             settingsLinkView.setVisibility(VISIBLE);
332             settingsLinkView.setOnClickListener((View view) -> {
333                 mAppSettingsClickListener.onClick(view, settingsIntent);
334             });
335         } else {
336             settingsLinkView.setVisibility(View.GONE);
337         }
338 
339         // System Settings button.
340         final View settingsButton = findViewById(R.id.info);
341         settingsButton.setOnClickListener(getSettingsOnClickListener());
342         settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
343     }
344 
getSettingsOnClickListener()345     private OnClickListener getSettingsOnClickListener() {
346         if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
347             final int appUidF = mAppUid;
348             return ((View view) -> {
349                 mOnSettingsClickListener.onClick(view,
350                         mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
351                         appUidF);
352             });
353         }
354         return null;
355     }
356 
getTurnOffNotificationsClickListener()357     private OnClickListener getTurnOffNotificationsClickListener() {
358         return ((View view) -> {
359             if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) {
360                 mPresentingChannelEditorDialog = true;
361 
362                 mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid,
363                         mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener);
364                 mChannelEditorDialogController.setOnFinishListener(() -> {
365                     mPresentingChannelEditorDialog = false;
366                     mGutsContainer.closeControls(this, false);
367                 });
368                 mChannelEditorDialogController.show();
369             }
370         });
371     }
372 
373     private void bindChannelDetails() throws RemoteException {
374         bindName();
375         bindGroup();
376     }
377 
378     private void bindName() {
379         final TextView channelName = findViewById(R.id.channel_name);
380         if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) {
381             channelName.setVisibility(View.GONE);
382         } else {
383             channelName.setText(mSingleNotificationChannel.getName());
384         }
385     }
386 
387     private void bindDelegate() {
388         TextView delegateView = findViewById(R.id.delegate_name);
389 
390         CharSequence delegatePkg = null;
391         if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
392             // this notification was posted by a delegate!
393             delegateView.setVisibility(View.VISIBLE);
394         } else {
395             delegateView.setVisibility(View.GONE);
396         }
397     }
398 
399     private void bindGroup() throws RemoteException {
400         // Set group information if this channel has an associated group.
401         CharSequence groupName = null;
402         if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) {
403             final NotificationChannelGroup notificationChannelGroup =
404                     mINotificationManager.getNotificationChannelGroupForPackage(
405                             mSingleNotificationChannel.getGroup(), mPackageName, mAppUid);
406             if (notificationChannelGroup != null) {
407                 groupName = notificationChannelGroup.getName();
408             }
409         }
410         TextView groupNameView = findViewById(R.id.group_name);
411         if (groupName != null) {
412             groupNameView.setText(groupName);
413             groupNameView.setVisibility(VISIBLE);
414         } else {
415             groupNameView.setVisibility(GONE);
416         }
417     }
418 
419     private void saveImportance() {
420         if (!mIsNonblockable) {
421             if (mChosenImportance == null) {
422                 mChosenImportance = mStartingChannelImportance;
423             }
424             updateImportance();
425         }
426     }
427 
428     /**
429      * Commits the updated importance values on the background thread.
430      */
431     private void updateImportance() {
432         if (mChosenImportance != null) {
433             logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE);
434             mMetricsLogger.write(importanceChangeLogMaker());
435 
436             int newImportance = mChosenImportance;
437             if (mStartingChannelImportance != IMPORTANCE_UNSPECIFIED) {
438                 if ((mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT)
439                         || (!mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)) {
440                     newImportance = mStartingChannelImportance;
441                 }
442             }
443 
444             Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
445             bgHandler.post(
446                     new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid,
447                             mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null,
448                             mStartingChannelImportance, newImportance, mIsAutomaticChosen));
449             mOnUserInteractionCallback.onImportanceChanged(mEntry);
450         }
451     }
452 
453     @Override
454     public boolean post(Runnable action) {
455         if (mSkipPost) {
456             action.run();
457             return true;
458         } else {
459             return super.post(action);
460         }
461     }
462 
463     private void applyAlertingBehavior(@AlertingBehavior int behavior, boolean userTriggered) {
464         if (userTriggered) {
465             TransitionSet transition = new TransitionSet();
466             transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
467             transition.addTransition(new Fade(Fade.OUT))
468                     .addTransition(new ChangeBounds())
469                     .addTransition(
470                             new Fade(Fade.IN)
471                                     .setStartDelay(150)
472                                     .setDuration(200)
473                                     .setInterpolator(FAST_OUT_SLOW_IN));
474             transition.setDuration(350);
475             transition.setInterpolator(FAST_OUT_SLOW_IN);
476             TransitionManager.beginDelayedTransition(this, transition);
477         }
478 
479         View alert = findViewById(R.id.alert);
480         View silence = findViewById(R.id.silence);
481         View automatic = findViewById(R.id.automatic);
482 
483         switch (behavior) {
484             case BEHAVIOR_ALERTING:
485                 mPriorityDescriptionView.setVisibility(VISIBLE);
486                 mSilentDescriptionView.setVisibility(GONE);
487                 mAutomaticDescriptionView.setVisibility(GONE);
488                 post(() -> {
489                     alert.setSelected(true);
490                     silence.setSelected(false);
491                     automatic.setSelected(false);
492                 });
493                 break;
494 
495             case BEHAVIOR_SILENT:
496                 mSilentDescriptionView.setVisibility(VISIBLE);
497                 mPriorityDescriptionView.setVisibility(GONE);
498                 mAutomaticDescriptionView.setVisibility(GONE);
499                 post(() -> {
500                     alert.setSelected(false);
501                     silence.setSelected(true);
502                     automatic.setSelected(false);
503                 });
504                 break;
505 
506             case BEHAVIOR_AUTOMATIC:
507                 mAutomaticDescriptionView.setVisibility(VISIBLE);
508                 mPriorityDescriptionView.setVisibility(GONE);
509                 mSilentDescriptionView.setVisibility(GONE);
510                 post(() -> {
511                     automatic.setSelected(true);
512                     alert.setSelected(false);
513                     silence.setSelected(false);
514                 });
515                 break;
516 
517             default:
518                 throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior);
519         }
520 
521         boolean isAChange = getAlertingBehavior() != behavior;
522         TextView done = findViewById(R.id.done);
523         done.setText(isAChange
524                 ? R.string.inline_ok_button
525                 : R.string.inline_done_button);
526     }
527 
528     @Override
529     public void onFinishedClosing() {
530         if (mChosenImportance != null) {
531             mStartingChannelImportance = mChosenImportance;
532         }
533 
534         bindInlineControls();
535 
536         logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
537         mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE));
538     }
539 
540     @Override
541     public boolean needsFalsingProtection() {
542         return true;
543     }
544 
545     @Override
546     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
547         super.onInitializeAccessibilityEvent(event);
548         if (mGutsContainer != null &&
549                 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
550             if (mGutsContainer.isExposed()) {
551                 event.getText().add(mContext.getString(
552                         R.string.notification_channel_controls_opened_accessibility, mAppName));
553             } else {
554                 event.getText().add(mContext.getString(
555                         R.string.notification_channel_controls_closed_accessibility, mAppName));
556             }
557         }
558     }
559 
560     private Intent getAppSettingsIntent(PackageManager pm, String packageName,
561             NotificationChannel channel, int id, String tag) {
562         Intent intent = new Intent(Intent.ACTION_MAIN)
563                 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
564                 .setPackage(packageName);
565         final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
566                 intent,
567                 PackageManager.MATCH_DEFAULT_ONLY
568         );
569         if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
570             return null;
571         }
572         final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
573         intent.setClassName(activityInfo.packageName, activityInfo.name);
574         if (channel != null) {
575             intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
576         }
577         intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
578         intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
579         return intent;
580     }
581 
582     @Override
583     public void setGutsParent(NotificationGuts guts) {
584         mGutsContainer = guts;
585     }
586 
587     @Override
588     public boolean willBeRemoved() {
589         return false;
590     }
591 
592     @Override
593     public boolean shouldBeSaved() {
594         return mPressedApply;
595     }
596 
597     @Override
598     public View getContentView() {
599         return this;
600     }
601 
602     @Override
603     public boolean handleCloseControls(boolean save, boolean force) {
604         if (mPresentingChannelEditorDialog && mChannelEditorDialogController != null) {
605             mPresentingChannelEditorDialog = false;
606             // No need for the finish listener because we're closing
607             mChannelEditorDialogController.setOnFinishListener(null);
608             mChannelEditorDialogController.close();
609         }
610 
611         // Save regardless of the importance so we can lock the importance field if the user wants
612         // to keep getting notifications
613         if (save) {
614             saveImportance();
615         }
616         return false;
617     }
618 
619     @Override
620     public int getActualHeight() {
621         // Because we're animating the bounds, getHeight will return the small height at the
622         // beginning of the animation. Instead we'd want it to already return the end value
623         return mActualHeight;
624     }
625 
626     @Override
627     protected void onLayout(boolean changed, int l, int t, int r, int b) {
628         super.onLayout(changed, l, t, r, b);
629         mActualHeight = getHeight();
630     }
631 
632     @VisibleForTesting
633     public boolean isAnimating() {
634         return false;
635     }
636 
637     /**
638      * Runnable to either update the given channel (with a new importance value) or, if no channel
639      * is provided, update notifications enabled state for the package.
640      */
641     private static class UpdateImportanceRunnable implements Runnable {
642         private final INotificationManager mINotificationManager;
643         private final String mPackageName;
644         private final int mAppUid;
645         private final @Nullable NotificationChannel mChannelToUpdate;
646         private final int mCurrentImportance;
647         private final int mNewImportance;
648         private final boolean mUnlockImportance;
649 
650 
651         public UpdateImportanceRunnable(INotificationManager notificationManager,
652                 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate,
653                 int currentImportance, int newImportance, boolean unlockImportance) {
654             mINotificationManager = notificationManager;
655             mPackageName = packageName;
656             mAppUid = appUid;
657             mChannelToUpdate = channelToUpdate;
658             mCurrentImportance = currentImportance;
659             mNewImportance = newImportance;
660             mUnlockImportance = unlockImportance;
661         }
662 
663         @Override
664         public void run() {
665             try {
666                 if (mChannelToUpdate != null) {
667                     if (mUnlockImportance) {
668                         mINotificationManager.unlockNotificationChannel(
669                                 mPackageName, mAppUid, mChannelToUpdate.getId());
670                     } else {
671                         mChannelToUpdate.setImportance(mNewImportance);
672                         mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
673                         mINotificationManager.updateNotificationChannelForPackage(
674                                 mPackageName, mAppUid, mChannelToUpdate);
675                     }
676                 } else {
677                     // For notifications with more than one channel, update notification enabled
678                     // state. If the importance was lowered, we disable notifications.
679                     mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage(
680                             mPackageName, mAppUid, mNewImportance >= mCurrentImportance);
681                 }
682             } catch (RemoteException e) {
683                 Log.e(TAG, "Unable to update notification importance", e);
684             }
685         }
686     }
687 
688     private void logUiEvent(NotificationControlsEvent event) {
689         if (mSbn != null) {
690             mUiEventLogger.logWithInstanceId(event,
691                     mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId());
692         }
693     }
694 
695     /**
696      * Returns a LogMaker with all available notification information.
697      * Caller should set category, type, and maybe subtype, before passing it to mMetricsLogger.
698      * @return LogMaker
699      */
700     private LogMaker getLogMaker() {
701         // The constructor requires a category, so also do it in the other branch for consistency.
702         return mSbn == null ? new LogMaker(MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
703                 : mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER);
704     }
705 
706     /**
707      * Returns an initialized LogMaker for logging importance changes.
708      * The caller may override the type before passing it to mMetricsLogger.
709      * @return LogMaker
710      */
711     private LogMaker importanceChangeLogMaker() {
712         Integer chosenImportance =
713                 mChosenImportance != null ? mChosenImportance : mStartingChannelImportance;
714         return getLogMaker().setCategory(MetricsEvent.ACTION_SAVE_IMPORTANCE)
715                 .setType(MetricsEvent.TYPE_ACTION)
716                 .setSubtype(chosenImportance - mStartingChannelImportance);
717     }
718 
719     /**
720      * Returns an initialized LogMaker for logging open/close of the info display.
721      * The caller may override the type before passing it to mMetricsLogger.
722      * @return LogMaker
723      */
724     private LogMaker notificationControlsLogMaker() {
725         return getLogMaker().setCategory(MetricsEvent.ACTION_NOTE_CONTROLS)
726                 .setType(MetricsEvent.TYPE_OPEN)
727                 .setSubtype(MetricsEvent.BLOCKING_HELPER_UNKNOWN);
728     }
729 
730     private @AlertingBehavior int getAlertingBehavior() {
731         if (mShowAutomaticSetting && !mSingleNotificationChannel.hasUserSetImportance()) {
732             return BEHAVIOR_AUTOMATIC;
733         }
734         return mWasShownHighPriority ? BEHAVIOR_ALERTING : BEHAVIOR_SILENT;
735     }
736 
737     @Retention(SOURCE)
738     @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_AUTOMATIC})
739     private @interface AlertingBehavior {}
740     private static final int BEHAVIOR_ALERTING = 0;
741     private static final int BEHAVIOR_SILENT = 1;
742     private static final int BEHAVIOR_AUTOMATIC = 2;
743 }
744