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