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