• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.shade.ShadeController;
72 import com.android.systemui.statusbar.notification.NotificationChannelHelper;
73 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
74 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
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.animateCollapseShade();
158             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
159         }
160         mGutsContainer.closeControls(v, /* save= */ 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( 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             ShortcutManager shortcutManager,
190             PackageManager pm,
191             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
192             INotificationManager iNotificationManager,
193             OnUserInteractionCallback onUserInteractionCallback,
194             String pkg,
195             NotificationChannel notificationChannel,
196             NotificationEntry entry,
197             Notification.BubbleMetadata bubbleMetadata,
198             OnSettingsClickListener onSettingsClick,
199             ConversationIconFactory conversationIconFactory,
200             Context userContext,
201             boolean isDeviceProvisioned,
202             @Main Handler mainHandler,
203             @Background Handler bgHandler,
204             OnConversationSettingsClickListener onConversationSettingsClickListener,
205             Optional<BubblesManager> bubblesManagerOptional,
206             ShadeController shadeController) {
207         mINotificationManager = iNotificationManager;
208         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
209         mOnUserInteractionCallback = onUserInteractionCallback;
210         mPackageName = pkg;
211         mEntry = entry;
212         mSbn = entry.getSbn();
213         mPm = pm;
214         mAppName = mPackageName;
215         mOnSettingsClickListener = onSettingsClick;
216         mNotificationChannel = notificationChannel;
217         mAppUid = mSbn.getUid();
218         mDelegatePkg = mSbn.getOpPkg();
219         mIsDeviceProvisioned = isDeviceProvisioned;
220         mOnConversationSettingsClickListener = onConversationSettingsClickListener;
221         mIconFactory = conversationIconFactory;
222         mUserContext = userContext;
223         mBubbleMetadata = bubbleMetadata;
224         mBubblesManagerOptional = bubblesManagerOptional;
225         mShadeController = shadeController;
226         mMainHandler = mainHandler;
227         mBgHandler = bgHandler;
228         mShortcutManager = shortcutManager;
229         mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
230         if (mShortcutInfo == null) {
231             throw new IllegalArgumentException("Does not have required information");
232         }
233 
234         mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded(
235                 getContext(), mINotificationManager, entry, mNotificationChannel);
236 
237         try {
238             mAppBubble = mINotificationManager.getBubblePreferenceForPackage(mPackageName, mAppUid);
239         } catch (RemoteException e) {
240             Log.e(TAG, "can't reach OS", e);
241             mAppBubble = BUBBLE_PREFERENCE_SELECTED;
242         }
243 
244         bindHeader();
245         bindActions();
246 
247         View done = findViewById(R.id.done);
248         done.setOnClickListener(mOnDone);
249         done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
250     }
251 
bindActions()252     private void bindActions() {
253 
254         // TODO: b/152050825
255         /*
256         Button home = findViewById(R.id.home);
257         home.setOnClickListener(mOnHomeClick);
258         home.setVisibility(mShortcutInfo != null
259                 && mShortcutManager.isRequestPinShortcutSupported()
260                 ? VISIBLE : GONE);
261 
262         Button snooze = findViewById(R.id.snooze);
263         snooze.setOnClickListener(mOnSnoozeClick);
264         */
265 
266         TextView defaultSummaryTextView = findViewById(R.id.default_summary);
267         if (mAppBubble == BUBBLE_PREFERENCE_ALL
268                 && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser())) {
269             defaultSummaryTextView.setText(getResources().getString(
270                     R.string.notification_channel_summary_default_with_bubbles, mAppName));
271         } else {
272             defaultSummaryTextView.setText(getResources().getString(
273                     R.string.notification_channel_summary_default));
274         }
275 
276         findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
277         findViewById(R.id.default_behavior).setOnClickListener(mOnDefaultClick);
278         findViewById(R.id.silence).setOnClickListener(mOnMuteClick);
279 
280         final View settingsButton = findViewById(R.id.info);
281         settingsButton.setOnClickListener(getSettingsOnClickListener());
282         settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
283 
284         updateToggleActions(mSelectedAction == -1 ? getPriority() : mSelectedAction,
285                 false);
286     }
287 
bindHeader()288     private void bindHeader() {
289         bindConversationDetails();
290 
291         // Delegate
292         bindDelegate();
293     }
294 
getSettingsOnClickListener()295     private OnClickListener getSettingsOnClickListener() {
296         if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
297             final int appUidF = mAppUid;
298             return ((View view) -> {
299                 mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
300             });
301         }
302         return null;
303     }
304 
bindConversationDetails()305     private void bindConversationDetails() {
306         final TextView channelName = findViewById(R.id.parent_channel_name);
307         channelName.setText(mNotificationChannel.getName());
308 
309         bindGroup();
310         // TODO: bring back when channel name does not include name
311         // bindName();
312         bindPackage();
313         bindIcon(mNotificationChannel.isImportantConversation());
314 
315         mPriorityDescriptionView = findViewById(R.id.priority_summary);
316         if (willShowAsBubble() && willBypassDnd()) {
317             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_all);
318         } else if (willShowAsBubble()) {
319             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_bubble);
320         } else if (willBypassDnd()) {
321             mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_dnd);
322         } else {
323             mPriorityDescriptionView.setText(
324                     R.string.notification_channel_summary_priority_baseline);
325         }
326     }
327 
bindIcon(boolean important)328     private void bindIcon(boolean important) {
329         Drawable person =  mIconFactory.getBaseIconDrawable(mShortcutInfo);
330         if (person == null) {
331             person = mContext.getDrawable(R.drawable.ic_person).mutate();
332             TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
333             int colorAccent = ta.getColor(0, 0);
334             ta.recycle();
335             person.setTint(colorAccent);
336         }
337         ImageView image = findViewById(R.id.conversation_icon);
338         image.setImageDrawable(person);
339 
340         ImageView app = findViewById(R.id.conversation_icon_badge_icon);
341         app.setImageDrawable(mIconFactory.getAppBadge(
342                         mPackageName, UserHandle.getUserId(mSbn.getUid())));
343 
344         findViewById(R.id.conversation_icon_badge_ring).setVisibility(important ? VISIBLE : GONE);
345     }
346 
bindPackage()347     private void bindPackage() {
348         ApplicationInfo info;
349         try {
350             info = mPm.getApplicationInfo(
351                     mPackageName,
352                     PackageManager.MATCH_UNINSTALLED_PACKAGES
353                             | PackageManager.MATCH_DISABLED_COMPONENTS
354                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
355                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
356             if (info != null) {
357                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
358             }
359         } catch (PackageManager.NameNotFoundException e) {
360         }
361         ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
362     }
363 
bindDelegate()364     private void bindDelegate() {
365         TextView delegateView = findViewById(R.id.delegate_name);
366 
367         if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
368             // this notification was posted by a delegate!
369             delegateView.setVisibility(View.VISIBLE);
370         } else {
371             delegateView.setVisibility(View.GONE);
372         }
373     }
374 
bindGroup()375     private void bindGroup() {
376         // Set group information if this channel has an associated group.
377         CharSequence groupName = null;
378         if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
379             try {
380                 final NotificationChannelGroup notificationChannelGroup =
381                         mINotificationManager.getNotificationChannelGroupForPackage(
382                                 mNotificationChannel.getGroup(), mPackageName, mAppUid);
383                 if (notificationChannelGroup != null) {
384                     groupName = notificationChannelGroup.getName();
385                 }
386             } catch (RemoteException e) {
387             }
388         }
389         TextView groupNameView = findViewById(R.id.group_name);
390         if (groupName != null) {
391             groupNameView.setText(groupName);
392             groupNameView.setVisibility(VISIBLE);
393         } else {
394             groupNameView.setVisibility(GONE);
395         }
396     }
397 
398     @Override
post(Runnable action)399     public boolean post(Runnable action) {
400         if (mSkipPost) {
401             action.run();
402             return true;
403         } else {
404             return super.post(action);
405         }
406     }
407 
408     @Override
onFinishInflate()409     protected void onFinishInflate() {
410         super.onFinishInflate();
411 
412         mDefaultDescriptionView = findViewById(R.id.default_summary);
413         mSilentDescriptionView = findViewById(R.id.silence_summary);
414     }
415 
416     @Override
onFinishedClosing()417     public void onFinishedClosing() { }
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
shouldBeSavedOnClose()562     public boolean shouldBeSavedOnClose() {
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 
577         // Clear the selected importance when closing, so when when we open again,
578         // we starts from a clean state.
579         mSelectedAction = -1;
580         mPressedApply = false;
581 
582         return false;
583     }
584 
585     @Override
getActualHeight()586     public int getActualHeight() {
587         // Because we're animating the bounds, getHeight will return the small height at the
588         // beginning of the animation. Instead we'd want it to already return the end value
589         return mActualHeight;
590     }
591 
592     @Override
onLayout(boolean changed, int l, int t, int r, int b)593     protected void onLayout(boolean changed, int l, int t, int r, int b) {
594         super.onLayout(changed, l, t, r, b);
595         mActualHeight = getHeight();
596     }
597 
598     @VisibleForTesting
isAnimating()599     public boolean isAnimating() {
600         return false;
601     }
602 
603     class UpdateChannelRunnable implements Runnable {
604 
605         private final INotificationManager mINotificationManager;
606         private final String mAppPkg;
607         private final int mAppUid;
608         private  NotificationChannel mChannelToUpdate;
609         private final @Action int mAction;
610 
UpdateChannelRunnable(INotificationManager notificationManager, String packageName, int appUid, @Action int action, @NonNull NotificationChannel channelToUpdate)611         public UpdateChannelRunnable(INotificationManager notificationManager,
612                 String packageName, int appUid, @Action int action,
613                 @NonNull NotificationChannel channelToUpdate) {
614             mINotificationManager = notificationManager;
615             mAppPkg = packageName;
616             mAppUid = appUid;
617             mChannelToUpdate = channelToUpdate;
618             mAction = action;
619         }
620 
621         @Override
run()622         public void run() {
623             try {
624                 switch (mAction) {
625                     case ACTION_FAVORITE:
626                         mChannelToUpdate.setImportantConversation(true);
627                         if (mChannelToUpdate.isImportantConversation()) {
628                             mChannelToUpdate.setAllowBubbles(true);
629                             if (mAppBubble == BUBBLE_PREFERENCE_NONE) {
630                                 mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
631                                         BUBBLE_PREFERENCE_SELECTED);
632                             }
633                             if (mBubblesManagerOptional.isPresent()) {
634                                 post(() -> mBubblesManagerOptional.get()
635                                         .onUserSetImportantConversation(mEntry));
636                             }
637                         }
638                         mChannelToUpdate.setImportance(Math.max(
639                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
640                         break;
641                     case ACTION_DEFAULT:
642                         mChannelToUpdate.setImportance(Math.max(
643                                 mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
644                         if (mChannelToUpdate.isImportantConversation()) {
645                             mChannelToUpdate.setImportantConversation(false);
646                             mChannelToUpdate.setAllowBubbles(false);
647                         }
648                         break;
649                     case ACTION_MUTE:
650                         if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
651                                 || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
652                             mChannelToUpdate.setImportance(IMPORTANCE_LOW);
653                         }
654                         if (mChannelToUpdate.isImportantConversation()) {
655                             mChannelToUpdate.setImportantConversation(false);
656                             mChannelToUpdate.setAllowBubbles(false);
657                         }
658                         break;
659                 }
660 
661                 mINotificationManager.updateNotificationChannelForPackage(
662                             mAppPkg, mAppUid, mChannelToUpdate);
663             } catch (RemoteException e) {
664                 Log.e(TAG, "Unable to update notification channel", e);
665             }
666         }
667     }
668 }
669