1 /*
2  * Copyright (C) 2020 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.BUBBLE_PREFERENCE_ALL;
20 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
21 import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
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.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
26 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
27 
28 import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
29 
30 import static java.lang.annotation.RetentionPolicy.SOURCE;
31 
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.app.INotificationManager;
36 import android.app.Notification;
37 import android.app.NotificationChannel;
38 import android.app.NotificationChannelGroup;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.pm.ApplicationInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.ShortcutInfo;
44 import android.content.pm.ShortcutManager;
45 import android.content.res.TypedArray;
46 import android.graphics.drawable.Drawable;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.RemoteException;
50 import android.os.UserHandle;
51 import android.service.notification.StatusBarNotification;
52 import android.text.TextUtils;
53 import android.transition.ChangeBounds;
54 import android.transition.Fade;
55 import android.transition.TransitionManager;
56 import android.transition.TransitionSet;
57 import android.util.AttributeSet;
58 import android.util.Log;
59 import android.view.View;
60 import android.view.accessibility.AccessibilityEvent;
61 import android.widget.ImageView;
62 import android.widget.LinearLayout;
63 import android.widget.TextView;
64 
65 import com.android.internal.annotations.VisibleForTesting;
66 import com.android.settingslib.notification.ConversationIconFactory;
67 import com.android.systemui.R;
68 import com.android.systemui.dagger.qualifiers.Background;
69 import com.android.systemui.dagger.qualifiers.Main;
70 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
71 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
72 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
73 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
74 import com.android.systemui.statusbar.phone.ShadeController;
75 import com.android.systemui.wmshell.BubblesManager;
76 
77 import java.lang.annotation.Retention;
78 import java.util.Optional;
79 
80 /**
81  * The guts of a conversation notification revealed when performing a long press.
82  */
83 public class NotificationConversationInfo extends LinearLayout implements
84         NotificationGuts.GutsContent {
85     private static final String TAG = "ConversationGuts";
86 
87     private INotificationManager mINotificationManager;
88     private ShortcutManager mShortcutManager;
89     private PackageManager mPm;
90     private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
91     private ConversationIconFactory mIconFactory;
92     private OnUserInteractionCallback mOnUserInteractionCallback;
93     private Handler mMainHandler;
94     private Handler mBgHandler;
95     private Optional<BubblesManager> mBubblesManagerOptional;
96     private ShadeController mShadeController;
97     private String mPackageName;
98     private String mAppName;
99     private int mAppUid;
100     private String mDelegatePkg;
101     private NotificationChannel mNotificationChannel;
102     private ShortcutInfo mShortcutInfo;
103     private NotificationEntry mEntry;
104     private StatusBarNotification mSbn;
105     @Nullable private Notification.BubbleMetadata mBubbleMetadata;
106     private Context mUserContext;
107     private boolean mIsDeviceProvisioned;
108     private int mAppBubble;
109 
110     private TextView mPriorityDescriptionView;
111     private TextView mDefaultDescriptionView;
112     private TextView mSilentDescriptionView;
113 
114     private @Action int mSelectedAction = -1;
115     private boolean mPressedApply;
116 
117     private OnSettingsClickListener mOnSettingsClickListener;
118     private NotificationGuts mGutsContainer;
119     private OnConversationSettingsClickListener mOnConversationSettingsClickListener;
120 
121     @VisibleForTesting
122     boolean mSkipPost = false;
123     private int mActualHeight;
124 
125     @Retention(SOURCE)
126     @IntDef({ACTION_DEFAULT, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
127             ACTION_SETTINGS})
128     private @interface Action {}
129     static final int ACTION_DEFAULT = 0;
130     static final int ACTION_HOME = 1;
131     static final int ACTION_FAVORITE = 2;
132     static final int ACTION_SNOOZE = 3;
133     static final int ACTION_MUTE = 4;
134     static final int ACTION_SETTINGS = 5;
135 
136     private OnClickListener mOnFavoriteClick = v -> {
137         setSelectedAction(ACTION_FAVORITE);
138         updateToggleActions(mSelectedAction, true);
139     };
140 
141     private OnClickListener mOnDefaultClick = v -> {
142         setSelectedAction(ACTION_DEFAULT);
143         updateToggleActions(mSelectedAction, true);
144     };
145 
146     private OnClickListener mOnMuteClick = v -> {
147         setSelectedAction(ACTION_MUTE);
148         updateToggleActions(mSelectedAction, true);
149     };
150 
151     private OnClickListener mOnDone = v -> {
152         mPressedApply = true;
153 
154         // If the user selected Priority and the previous selection was not priority, show a
155         // People Tile add request.
156         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
157             mShadeController.animateCollapsePanels();
158             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
159         }
160         mGutsContainer.closeControls(v, true);
161     };
162 
NotificationConversationInfo(Context context, AttributeSet attrs)163     public NotificationConversationInfo(Context context, AttributeSet attrs) {
164         super(context, attrs);
165     }
166 
167     public interface OnSettingsClickListener {
onClick(View v, NotificationChannel channel, int appUid)168         void onClick(View v, NotificationChannel channel, int appUid);
169     }
170 
171     public interface OnConversationSettingsClickListener {
onClick()172         void onClick();
173     }
174 
175     public interface OnAppSettingsClickListener {
onClick(View v, Intent intent)176         void onClick(View v, Intent intent);
177     }
178 
179     @VisibleForTesting
setSelectedAction(int selectedAction)180     void setSelectedAction(int selectedAction) {
181         if (mSelectedAction == selectedAction) {
182             return;
183         }
184 
185         mSelectedAction = selectedAction;
186     }
187 
bindNotification( @ction int selectedAction, ShortcutManager shortcutManager, PackageManager pm, PeopleSpaceWidgetManager peopleSpaceWidgetManager, INotificationManager iNotificationManager, OnUserInteractionCallback onUserInteractionCallback, String pkg, NotificationChannel notificationChannel, NotificationEntry entry, Notification.BubbleMetadata bubbleMetadata, OnSettingsClickListener onSettingsClick, ConversationIconFactory conversationIconFactory, Context userContext, boolean isDeviceProvisioned, @Main Handler mainHandler, @Background Handler bgHandler, OnConversationSettingsClickListener onConversationSettingsClickListener, Optional<BubblesManager> bubblesManagerOptional, ShadeController shadeController)188     public void bindNotification(
189             @Action int selectedAction,
190             ShortcutManager shortcutManager,
191             PackageManager pm,
192             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
193             INotificationManager iNotificationManager,
194             OnUserInteractionCallback onUserInteractionCallback,
195             String pkg,
196             NotificationChannel notificationChannel,
197             NotificationEntry entry,
198             Notification.BubbleMetadata bubbleMetadata,
199             OnSettingsClickListener onSettingsClick,
200             ConversationIconFactory conversationIconFactory,
201             Context userContext,
202             boolean isDeviceProvisioned,
203             @Main Handler mainHandler,
204             @Background Handler bgHandler,
205             OnConversationSettingsClickListener onConversationSettingsClickListener,
206             Optional<BubblesManager> bubblesManagerOptional,
207             ShadeController shadeController) {
208         mPressedApply = false;
209         mSelectedAction = selectedAction;
210         mINotificationManager = iNotificationManager;
211         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
212         mOnUserInteractionCallback = onUserInteractionCallback;
213         mPackageName = pkg;
214         mEntry = entry;
215         mSbn = entry.getSbn();
216         mPm = pm;
217         mAppName = mPackageName;
218         mOnSettingsClickListener = onSettingsClick;
219         mNotificationChannel = notificationChannel;
220         mAppUid = mSbn.getUid();
221         mDelegatePkg = mSbn.getOpPkg();
222         mIsDeviceProvisioned = isDeviceProvisioned;
223         mOnConversationSettingsClickListener = onConversationSettingsClickListener;
224         mIconFactory = conversationIconFactory;
225         mUserContext = userContext;
226         mBubbleMetadata = bubbleMetadata;
227         mBubblesManagerOptional = bubblesManagerOptional;
228         mShadeController = shadeController;
229         mMainHandler = mainHandler;
230         mBgHandler = bgHandler;
231         mShortcutManager = shortcutManager;
232         mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
233         if (mShortcutInfo == null) {
234             throw new IllegalArgumentException("Does not have required information");
235         }
236 
237         mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded(
238                 getContext(), mINotificationManager, entry, mNotificationChannel);
239 
240         try {
241             mAppBubble = mINotificationManager.getBubblePreferenceForPackage(mPackageName, mAppUid);
242         } catch (RemoteException e) {
243             Log.e(TAG, "can't reach OS", e);
244             mAppBubble = BUBBLE_PREFERENCE_SELECTED;
245         }
246 
247         bindHeader();
248         bindActions();
249 
250         View done = findViewById(R.id.done);
251         done.setOnClickListener(mOnDone);
252         done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
253     }
254 
bindActions()255     private void bindActions() {
256 
257         // TODO: b/152050825
258         /*
259         Button home = findViewById(R.id.home);
260         home.setOnClickListener(mOnHomeClick);
261         home.setVisibility(mShortcutInfo != null
262                 && mShortcutManager.isRequestPinShortcutSupported()
263                 ? VISIBLE : GONE);
264 
265         Button snooze = findViewById(R.id.snooze);
266         snooze.setOnClickListener(mOnSnoozeClick);
267         */
268 
269         if (mAppBubble == BUBBLE_PREFERENCE_ALL) {
270             ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString(
271                     R.string.notification_channel_summary_default_with_bubbles, mAppName));
272         }
273 
274         findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
275         findViewById(R.id.default_behavior).setOnClickListener(mOnDefaultClick);
276         findViewById(R.id.silence).setOnClickListener(mOnMuteClick);
277 
278         final View settingsButton = findViewById(R.id.info);
279         settingsButton.setOnClickListener(getSettingsOnClickListener());
280         settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
281 
282         updateToggleActions(mSelectedAction == -1 ? getPriority() : mSelectedAction,
283                 false);
284     }
285 
bindHeader()286     private void bindHeader() {
287         bindConversationDetails();
288 
289         // Delegate
290         bindDelegate();
291     }
292 
getSettingsOnClickListener()293     private OnClickListener getSettingsOnClickListener() {
294         if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
295             final int appUidF = mAppUid;
296             return ((View view) -> {
297                 mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
298             });
299         }
300         return null;
301     }
302 
bindConversationDetails()303     private void bindConversationDetails() {
304         final TextView channelName = findViewById(R.id.parent_channel_name);
305         channelName.setText(mNotificationChannel.getName());
306 
307         bindGroup();
308         // TODO: bring back when channel name does not include name
309         // bindName();
310         bindPackage();
311         bindIcon(mNotificationChannel.isImportantConversation());
312 
313         mPriorityDescriptionView = findViewById(R.id.priority_summary);
314         if (willShowAsBubble() && willBypassDnd()) {
315             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_all);
316         } else if (willShowAsBubble()) {
317             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_bubble);
318         } else if (willBypassDnd()) {
319             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_dnd);
320         } else {
321             mPriorityDescriptionView.setText(
322                     R.string.notification_channel_summary_priority_baseline);
323         }
324     }
325 
bindIcon(boolean important)326     private void bindIcon(boolean important) {
327         Drawable person =  mIconFactory.getBaseIconDrawable(mShortcutInfo);
328         if (person == null) {
329             person = mContext.getDrawable(R.drawable.ic_person).mutate();
330             TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
331             int colorAccent = ta.getColor(0, 0);
332             ta.recycle();
333             person.setTint(colorAccent);
334         }
335         ImageView image = findViewById(R.id.conversation_icon);
336         image.setImageDrawable(person);
337 
338         ImageView app = findViewById(R.id.conversation_icon_badge_icon);
339         app.setImageDrawable(mIconFactory.getAppBadge(
340                         mPackageName, UserHandle.getUserId(mSbn.getUid())));
341 
342         findViewById(R.id.conversation_icon_badge_ring).setVisibility(important ? VISIBLE : GONE);
343     }
344 
bindPackage()345     private void bindPackage() {
346         ApplicationInfo info;
347         try {
348             info = mPm.getApplicationInfo(
349                     mPackageName,
350                     PackageManager.MATCH_UNINSTALLED_PACKAGES
351                             | PackageManager.MATCH_DISABLED_COMPONENTS
352                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
353                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
354             if (info != null) {
355                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
356             }
357         } catch (PackageManager.NameNotFoundException e) {
358         }
359         ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
360     }
361 
bindDelegate()362     private void bindDelegate() {
363         TextView delegateView = findViewById(R.id.delegate_name);
364 
365         if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
366             // this notification was posted by a delegate!
367             delegateView.setVisibility(View.VISIBLE);
368         } else {
369             delegateView.setVisibility(View.GONE);
370         }
371     }
372 
bindGroup()373     private void bindGroup() {
374         // Set group information if this channel has an associated group.
375         CharSequence groupName = null;
376         if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
377             try {
378                 final NotificationChannelGroup notificationChannelGroup =
379                         mINotificationManager.getNotificationChannelGroupForPackage(
380                                 mNotificationChannel.getGroup(), mPackageName, mAppUid);
381                 if (notificationChannelGroup != null) {
382                     groupName = notificationChannelGroup.getName();
383                 }
384             } catch (RemoteException e) {
385             }
386         }
387         TextView groupNameView = findViewById(R.id.group_name);
388         if (groupName != null) {
389             groupNameView.setText(groupName);
390             groupNameView.setVisibility(VISIBLE);
391         } else {
392             groupNameView.setVisibility(GONE);
393         }
394     }
395 
396     @Override
post(Runnable action)397     public boolean post(Runnable action) {
398         if (mSkipPost) {
399             action.run();
400             return true;
401         } else {
402             return super.post(action);
403         }
404     }
405 
406     @Override
onFinishInflate()407     protected void onFinishInflate() {
408         super.onFinishInflate();
409 
410         mDefaultDescriptionView = findViewById(R.id.default_summary);
411         mSilentDescriptionView = findViewById(R.id.silence_summary);
412     }
413 
414     @Override
onFinishedClosing()415     public void onFinishedClosing() {
416         mSelectedAction = -1;
417     }
418 
419     @Override
needsFalsingProtection()420     public boolean needsFalsingProtection() {
421         return true;
422     }
423 
424     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)425     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
426         super.onInitializeAccessibilityEvent(event);
427         if (mGutsContainer != null &&
428                 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
429             if (mGutsContainer.isExposed()) {
430                 event.getText().add(mContext.getString(
431                         R.string.notification_channel_controls_opened_accessibility, mAppName));
432             } else {
433                 event.getText().add(mContext.getString(
434                         R.string.notification_channel_controls_closed_accessibility, mAppName));
435             }
436         }
437     }
438 
updateToggleActions(int selectedAction, boolean userTriggered)439     private void updateToggleActions(int selectedAction, boolean userTriggered) {
440         if (userTriggered) {
441             TransitionSet transition = new TransitionSet();
442             transition.setOrdering(TransitionSet.ORDERING_TOGETHER);
443             transition.addTransition(new Fade(Fade.OUT))
444                     .addTransition(new ChangeBounds())
445                     .addTransition(
446                             new Fade(Fade.IN)
447                                     .setStartDelay(150)
448                                     .setDuration(200)
449                                     .setInterpolator(FAST_OUT_SLOW_IN));
450             transition.setDuration(350);
451             transition.setInterpolator(FAST_OUT_SLOW_IN);
452             TransitionManager.beginDelayedTransition(this, transition);
453         }
454 
455         View priority = findViewById(R.id.priority);
456         View defaultBehavior = findViewById(R.id.default_behavior);
457         View silence = findViewById(R.id.silence);
458 
459         switch (selectedAction) {
460             case ACTION_FAVORITE:
461                 mPriorityDescriptionView.setVisibility(VISIBLE);
462                 mDefaultDescriptionView.setVisibility(GONE);
463                 mSilentDescriptionView.setVisibility(GONE);
464                 post(() -> {
465                     priority.setSelected(true);
466                     defaultBehavior.setSelected(false);
467                     silence.setSelected(false);
468                 });
469                 break;
470 
471             case ACTION_MUTE:
472                 mSilentDescriptionView.setVisibility(VISIBLE);
473                 mDefaultDescriptionView.setVisibility(GONE);
474                 mPriorityDescriptionView.setVisibility(GONE);
475                 post(() -> {
476                     priority.setSelected(false);
477                     defaultBehavior.setSelected(false);
478                     silence.setSelected(true);
479                 });
480                 break;
481 
482             case ACTION_DEFAULT:
483                 mDefaultDescriptionView.setVisibility(VISIBLE);
484                 mSilentDescriptionView.setVisibility(GONE);
485                 mPriorityDescriptionView.setVisibility(GONE);
486                 post(() -> {
487                     priority.setSelected(false);
488                     defaultBehavior.setSelected(true);
489                     silence.setSelected(false);
490                 });
491                 break;
492 
493             default:
494                 throw new IllegalArgumentException("Unrecognized behavior: " + mSelectedAction);
495         }
496 
497         boolean isAChange = getPriority() != selectedAction;
498         TextView done = findViewById(R.id.done);
499         done.setText(isAChange
500                 ? R.string.inline_ok_button
501                 : R.string.inline_done_button);
502 
503         // update icon in case importance has changed
504         bindIcon(selectedAction == ACTION_FAVORITE);
505     }
506 
getSelectedAction()507     int getSelectedAction() {
508         return mSelectedAction;
509     }
510 
getPriority()511     private int getPriority() {
512         if (mNotificationChannel.getImportance() <= IMPORTANCE_LOW
513                 && mNotificationChannel.getImportance() > IMPORTANCE_UNSPECIFIED) {
514             return ACTION_MUTE;
515         } else {
516             if (mNotificationChannel.isImportantConversation()) {
517                 return ACTION_FAVORITE;
518             }
519         }
520         return ACTION_DEFAULT;
521     }
522 
updateChannel()523     private void updateChannel() {
524         mBgHandler.post(
525                 new UpdateChannelRunnable(mINotificationManager, mPackageName,
526                         mAppUid, mSelectedAction, mNotificationChannel));
527         mEntry.markForUserTriggeredMovement(true);
528         mMainHandler.postDelayed(
529                 () -> mOnUserInteractionCallback.onImportanceChanged(mEntry),
530                 StackStateAnimator.ANIMATION_DURATION_STANDARD);
531     }
532 
willBypassDnd()533     private boolean willBypassDnd() {
534         boolean bypassesDnd = false;
535         try {
536             int allowedSenders = mINotificationManager
537                     .getConsolidatedNotificationPolicy().priorityConversationSenders;
538             bypassesDnd =  allowedSenders == CONVERSATION_SENDERS_IMPORTANT
539                     || allowedSenders == CONVERSATION_SENDERS_ANYONE;
540         } catch (RemoteException e) {
541             Log.e(TAG, "Could not check conversation senders", e);
542         }
543         return bypassesDnd;
544     }
545 
willShowAsBubble()546     private boolean willShowAsBubble() {
547         return mBubbleMetadata != null
548                 && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser());
549     }
550 
551     @Override
setGutsParent(NotificationGuts guts)552     public void setGutsParent(NotificationGuts guts) {
553         mGutsContainer = guts;
554     }
555 
556     @Override
willBeRemoved()557     public boolean willBeRemoved() {
558         return false;
559     }
560 
561     @Override
shouldBeSaved()562     public boolean shouldBeSaved() {
563         return mPressedApply;
564     }
565 
566     @Override
getContentView()567     public View getContentView() {
568         return this;
569     }
570 
571     @Override
handleCloseControls(boolean save, boolean force)572     public boolean handleCloseControls(boolean save, boolean force) {
573         if (save && mSelectedAction > -1) {
574             updateChannel();
575         }
576         return false;
577     }
578 
579     @Override
getActualHeight()580     public int getActualHeight() {
581         // Because we're animating the bounds, getHeight will return the small height at the
582         // beginning of the animation. Instead we'd want it to already return the end value
583         return mActualHeight;
584     }
585 
586     @Override
onLayout(boolean changed, int l, int t, int r, int b)587     protected void onLayout(boolean changed, int l, int t, int r, int b) {
588         super.onLayout(changed, l, t, r, b);
589         mActualHeight = getHeight();
590     }
591 
592     @VisibleForTesting
isAnimating()593     public boolean isAnimating() {
594         return false;
595     }
596 
597     class UpdateChannelRunnable implements Runnable {
598 
599         private final INotificationManager mINotificationManager;
600         private final String mAppPkg;
601         private final int mAppUid;
602         private  NotificationChannel mChannelToUpdate;
603         private final @Action int mAction;
604 
UpdateChannelRunnable(INotificationManager notificationManager, String packageName, int appUid, @Action int action, @NonNull NotificationChannel channelToUpdate)605         public UpdateChannelRunnable(INotificationManager notificationManager,
606                 String packageName, int appUid, @Action int action,
607                 @NonNull NotificationChannel channelToUpdate) {
608             mINotificationManager = notificationManager;
609             mAppPkg = packageName;
610             mAppUid = appUid;
611             mChannelToUpdate = channelToUpdate;
612             mAction = action;
613         }
614 
615         @Override
run()616         public void run() {
617             try {
618                 switch (mAction) {
619                     case ACTION_FAVORITE:
620                         mChannelToUpdate.setImportantConversation(true);
621                         if (mChannelToUpdate.isImportantConversation()) {
622                             mChannelToUpdate.setAllowBubbles(true);
623                             if (mAppBubble == BUBBLE_PREFERENCE_NONE) {
624                                 mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
625                                         BUBBLE_PREFERENCE_SELECTED);
626                             }
627                             if (mBubblesManagerOptional.isPresent()) {
628                                 post(() -> mBubblesManagerOptional.get()
629                                         .onUserSetImportantConversation(mEntry));
630                             }
631                         }
632                         mChannelToUpdate.setImportance(Math.max(
633                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
634                         break;
635                     case ACTION_DEFAULT:
636                         mChannelToUpdate.setImportance(Math.max(
637                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
638                         if (mChannelToUpdate.isImportantConversation()) {
639                             mChannelToUpdate.setImportantConversation(false);
640                             mChannelToUpdate.setAllowBubbles(false);
641                         }
642                         break;
643                     case ACTION_MUTE:
644                         if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
645                                 || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
646                             mChannelToUpdate.setImportance(IMPORTANCE_LOW);
647                         }
648                         if (mChannelToUpdate.isImportantConversation()) {
649                             mChannelToUpdate.setImportantConversation(false);
650                             mChannelToUpdate.setAllowBubbles(false);
651                         }
652                         break;
653                 }
654 
655                 mINotificationManager.updateNotificationChannelForPackage(
656                             mAppPkg, mAppUid, mChannelToUpdate);
657             } catch (RemoteException e) {
658                 Log.e(TAG, "Unable to update notification channel", e);
659             }
660         }
661     }
662 }
663