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