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