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