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