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