1 /* 2 * Copyright (C) 2017 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.IMPORTANCE_DEFAULT; 20 import static android.app.NotificationManager.IMPORTANCE_LOW; 21 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 22 23 import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN; 24 25 import static java.lang.annotation.RetentionPolicy.SOURCE; 26 27 import android.annotation.IntDef; 28 import android.annotation.Nullable; 29 import android.app.INotificationManager; 30 import android.app.Notification; 31 import android.app.NotificationChannel; 32 import android.app.NotificationChannelGroup; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.ActivityInfo; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.PackageManager; 38 import android.content.pm.ResolveInfo; 39 import android.graphics.drawable.Drawable; 40 import android.metrics.LogMaker; 41 import android.os.Handler; 42 import android.os.RemoteException; 43 import android.service.notification.StatusBarNotification; 44 import android.text.Html; 45 import android.text.TextUtils; 46 import android.transition.ChangeBounds; 47 import android.transition.Fade; 48 import android.transition.TransitionManager; 49 import android.transition.TransitionSet; 50 import android.util.AttributeSet; 51 import android.util.Log; 52 import android.view.View; 53 import android.view.accessibility.AccessibilityEvent; 54 import android.widget.ImageView; 55 import android.widget.LinearLayout; 56 import android.widget.TextView; 57 58 import com.android.internal.annotations.VisibleForTesting; 59 import com.android.internal.logging.MetricsLogger; 60 import com.android.internal.logging.UiEventLogger; 61 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 62 import com.android.systemui.Dependency; 63 import com.android.systemui.R; 64 import com.android.systemui.statusbar.notification.AssistantFeedbackController; 65 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 66 67 import java.lang.annotation.Retention; 68 import java.util.List; 69 import java.util.Set; 70 71 /** 72 * The guts of a notification revealed when performing a long press. 73 */ 74 public class NotificationInfo extends LinearLayout implements NotificationGuts.GutsContent { 75 private static final String TAG = "InfoGuts"; 76 private int mActualHeight; 77 78 @IntDef(prefix = { "ACTION_" }, value = { 79 ACTION_NONE, 80 ACTION_TOGGLE_ALERT, 81 ACTION_TOGGLE_SILENT, 82 }) 83 public @interface NotificationInfoAction { 84 } 85 86 public static final int ACTION_NONE = 0; 87 // standard controls 88 static final int ACTION_TOGGLE_SILENT = 2; 89 // standard controls 90 private static final int ACTION_TOGGLE_ALERT = 5; 91 92 private TextView mPriorityDescriptionView; 93 private TextView mSilentDescriptionView; 94 private TextView mAutomaticDescriptionView; 95 96 private INotificationManager mINotificationManager; 97 private OnUserInteractionCallback mOnUserInteractionCallback; 98 private PackageManager mPm; 99 private MetricsLogger mMetricsLogger; 100 private ChannelEditorDialogController mChannelEditorDialogController; 101 private AssistantFeedbackController mAssistantFeedbackController; 102 103 private String mPackageName; 104 private String mAppName; 105 private int mAppUid; 106 private String mDelegatePkg; 107 private int mNumUniqueChannelsInRow; 108 private Set<NotificationChannel> mUniqueChannelsInRow; 109 private NotificationChannel mSingleNotificationChannel; 110 private int mStartingChannelImportance; 111 private boolean mWasShownHighPriority; 112 private boolean mPressedApply; 113 private boolean mPresentingChannelEditorDialog = false; 114 private boolean mShowAutomaticSetting; 115 116 /** 117 * The last importance level chosen by the user. Null if the user has not chosen an importance 118 * level; non-null once the user takes an action which indicates an explicit preference. 119 */ 120 @Nullable private Integer mChosenImportance; 121 private boolean mIsAutomaticChosen; 122 private boolean mIsSingleDefaultChannel; 123 private boolean mIsNonblockable; 124 private NotificationEntry mEntry; 125 private StatusBarNotification mSbn; 126 private boolean mIsDeviceProvisioned; 127 128 private OnSettingsClickListener mOnSettingsClickListener; 129 private OnAppSettingsClickListener mAppSettingsClickListener; 130 private NotificationGuts mGutsContainer; 131 private Drawable mPkgIcon; 132 private UiEventLogger mUiEventLogger; 133 134 @VisibleForTesting 135 boolean mSkipPost = false; 136 137 // used by standard ui 138 private OnClickListener mOnAutomatic = v -> { 139 mIsAutomaticChosen = true; 140 applyAlertingBehavior(BEHAVIOR_AUTOMATIC, true /* userTriggered */); 141 }; 142 143 // used by standard ui 144 private OnClickListener mOnAlert = v -> { 145 mChosenImportance = IMPORTANCE_DEFAULT; 146 mIsAutomaticChosen = false; 147 applyAlertingBehavior(BEHAVIOR_ALERTING, true /* userTriggered */); 148 }; 149 150 // used by standard ui 151 private OnClickListener mOnSilent = v -> { 152 mChosenImportance = IMPORTANCE_LOW; 153 mIsAutomaticChosen = false; 154 applyAlertingBehavior(BEHAVIOR_SILENT, true /* userTriggered */); 155 }; 156 157 // used by standard ui 158 private OnClickListener mOnDismissSettings = v -> { 159 mPressedApply = true; 160 mGutsContainer.closeControls(v, true); 161 }; 162 NotificationInfo(Context context, AttributeSet attrs)163 public NotificationInfo(Context context, AttributeSet attrs) { 164 super(context, attrs); 165 } 166 167 @Override onFinishInflate()168 protected void onFinishInflate() { 169 super.onFinishInflate(); 170 171 mPriorityDescriptionView = findViewById(R.id.alert_summary); 172 mSilentDescriptionView = findViewById(R.id.silence_summary); 173 mAutomaticDescriptionView = findViewById(R.id.automatic_summary); 174 } 175 176 // Specify a CheckSaveListener to override when/if the user's changes are committed. 177 public interface CheckSaveListener { 178 // Invoked when importance has changed and the NotificationInfo wants to try to save it. 179 // Listener should run saveImportance unless the change should be canceled. checkSave(Runnable saveImportance, StatusBarNotification sbn)180 void checkSave(Runnable saveImportance, StatusBarNotification sbn); 181 } 182 183 public interface OnSettingsClickListener { onClick(View v, NotificationChannel channel, int appUid)184 void onClick(View v, NotificationChannel channel, int appUid); 185 } 186 187 public interface OnAppSettingsClickListener { onClick(View v, Intent intent)188 void onClick(View v, Intent intent); 189 } 190 bindNotification( PackageManager pm, INotificationManager iNotificationManager, OnUserInteractionCallback onUserInteractionCallback, ChannelEditorDialogController channelEditorDialogController, String pkg, NotificationChannel notificationChannel, Set<NotificationChannel> uniqueChannelsInRow, NotificationEntry entry, OnSettingsClickListener onSettingsClick, OnAppSettingsClickListener onAppSettingsClick, UiEventLogger uiEventLogger, boolean isDeviceProvisioned, boolean isNonblockable, boolean wasShownHighPriority, AssistantFeedbackController assistantFeedbackController)191 public void bindNotification( 192 PackageManager pm, 193 INotificationManager iNotificationManager, 194 OnUserInteractionCallback onUserInteractionCallback, 195 ChannelEditorDialogController channelEditorDialogController, 196 String pkg, 197 NotificationChannel notificationChannel, 198 Set<NotificationChannel> uniqueChannelsInRow, 199 NotificationEntry entry, 200 OnSettingsClickListener onSettingsClick, 201 OnAppSettingsClickListener onAppSettingsClick, 202 UiEventLogger uiEventLogger, 203 boolean isDeviceProvisioned, 204 boolean isNonblockable, 205 boolean wasShownHighPriority, 206 AssistantFeedbackController assistantFeedbackController) 207 throws RemoteException { 208 mINotificationManager = iNotificationManager; 209 mMetricsLogger = Dependency.get(MetricsLogger.class); 210 mOnUserInteractionCallback = onUserInteractionCallback; 211 mChannelEditorDialogController = channelEditorDialogController; 212 mAssistantFeedbackController = assistantFeedbackController; 213 mPackageName = pkg; 214 mUniqueChannelsInRow = uniqueChannelsInRow; 215 mNumUniqueChannelsInRow = uniqueChannelsInRow.size(); 216 mEntry = entry; 217 mSbn = entry.getSbn(); 218 mPm = pm; 219 mAppSettingsClickListener = onAppSettingsClick; 220 mAppName = mPackageName; 221 mOnSettingsClickListener = onSettingsClick; 222 mSingleNotificationChannel = notificationChannel; 223 mStartingChannelImportance = mSingleNotificationChannel.getImportance(); 224 mWasShownHighPriority = wasShownHighPriority; 225 mIsNonblockable = isNonblockable; 226 mAppUid = mSbn.getUid(); 227 mDelegatePkg = mSbn.getOpPkg(); 228 mIsDeviceProvisioned = isDeviceProvisioned; 229 mShowAutomaticSetting = mAssistantFeedbackController.isFeedbackEnabled(); 230 mUiEventLogger = uiEventLogger; 231 232 int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( 233 pkg, mAppUid, false /* includeDeleted */); 234 if (mNumUniqueChannelsInRow == 0) { 235 throw new IllegalArgumentException("bindNotification requires at least one channel"); 236 } else { 237 // Special behavior for the Default channel if no other channels have been defined. 238 mIsSingleDefaultChannel = mNumUniqueChannelsInRow == 1 239 && mSingleNotificationChannel.getId().equals( 240 NotificationChannel.DEFAULT_CHANNEL_ID) 241 && numTotalChannels == 1; 242 } 243 mIsAutomaticChosen = getAlertingBehavior() == BEHAVIOR_AUTOMATIC; 244 245 bindHeader(); 246 bindChannelDetails(); 247 248 bindInlineControls(); 249 250 logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_OPEN); 251 mMetricsLogger.write(notificationControlsLogMaker()); 252 } 253 bindInlineControls()254 private void bindInlineControls() { 255 if (mIsNonblockable) { 256 findViewById(R.id.non_configurable_text).setVisibility(VISIBLE); 257 findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE); 258 findViewById(R.id.interruptiveness_settings).setVisibility(GONE); 259 ((TextView) findViewById(R.id.done)).setText(R.string.inline_done_button); 260 findViewById(R.id.turn_off_notifications).setVisibility(GONE); 261 } else if (mNumUniqueChannelsInRow > 1) { 262 findViewById(R.id.non_configurable_text).setVisibility(GONE); 263 findViewById(R.id.interruptiveness_settings).setVisibility(GONE); 264 findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE); 265 } else { 266 findViewById(R.id.non_configurable_text).setVisibility(GONE); 267 findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE); 268 findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE); 269 } 270 271 View turnOffButton = findViewById(R.id.turn_off_notifications); 272 turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener()); 273 turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable 274 ? VISIBLE : GONE); 275 276 View done = findViewById(R.id.done); 277 done.setOnClickListener(mOnDismissSettings); 278 done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate()); 279 280 View silent = findViewById(R.id.silence); 281 View alert = findViewById(R.id.alert); 282 silent.setOnClickListener(mOnSilent); 283 alert.setOnClickListener(mOnAlert); 284 285 View automatic = findViewById(R.id.automatic); 286 if (mShowAutomaticSetting) { 287 mAutomaticDescriptionView.setText(Html.fromHtml(mContext.getText( 288 mAssistantFeedbackController.getInlineDescriptionResource(mEntry)).toString())); 289 automatic.setVisibility(VISIBLE); 290 automatic.setOnClickListener(mOnAutomatic); 291 } else { 292 automatic.setVisibility(GONE); 293 } 294 295 int behavior = getAlertingBehavior(); 296 applyAlertingBehavior(behavior, false /* userTriggered */); 297 } 298 bindHeader()299 private void bindHeader() { 300 // Package name 301 mPkgIcon = null; 302 ApplicationInfo info; 303 try { 304 info = mPm.getApplicationInfo( 305 mPackageName, 306 PackageManager.MATCH_UNINSTALLED_PACKAGES 307 | PackageManager.MATCH_DISABLED_COMPONENTS 308 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 309 | PackageManager.MATCH_DIRECT_BOOT_AWARE); 310 if (info != null) { 311 mAppName = String.valueOf(mPm.getApplicationLabel(info)); 312 mPkgIcon = mPm.getApplicationIcon(info); 313 } 314 } catch (PackageManager.NameNotFoundException e) { 315 // app is gone, just show package name and generic icon 316 mPkgIcon = mPm.getDefaultActivityIcon(); 317 } 318 ((ImageView) findViewById(R.id.pkg_icon)).setImageDrawable(mPkgIcon); 319 ((TextView) findViewById(R.id.pkg_name)).setText(mAppName); 320 321 // Delegate 322 bindDelegate(); 323 324 // Set up app settings link (i.e. Customize) 325 View settingsLinkView = findViewById(R.id.app_settings); 326 Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName, 327 mSingleNotificationChannel, 328 mSbn.getId(), mSbn.getTag()); 329 if (settingsIntent != null 330 && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) { 331 settingsLinkView.setVisibility(VISIBLE); 332 settingsLinkView.setOnClickListener((View view) -> { 333 mAppSettingsClickListener.onClick(view, settingsIntent); 334 }); 335 } else { 336 settingsLinkView.setVisibility(View.GONE); 337 } 338 339 // System Settings button. 340 final View settingsButton = findViewById(R.id.info); 341 settingsButton.setOnClickListener(getSettingsOnClickListener()); 342 settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE); 343 } 344 getSettingsOnClickListener()345 private OnClickListener getSettingsOnClickListener() { 346 if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) { 347 final int appUidF = mAppUid; 348 return ((View view) -> { 349 mOnSettingsClickListener.onClick(view, 350 mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel, 351 appUidF); 352 }); 353 } 354 return null; 355 } 356 getTurnOffNotificationsClickListener()357 private OnClickListener getTurnOffNotificationsClickListener() { 358 return ((View view) -> { 359 if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) { 360 mPresentingChannelEditorDialog = true; 361 362 mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid, 363 mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener); 364 mChannelEditorDialogController.setOnFinishListener(() -> { 365 mPresentingChannelEditorDialog = false; 366 mGutsContainer.closeControls(this, false); 367 }); 368 mChannelEditorDialogController.show(); 369 } 370 }); 371 } 372 373 private void bindChannelDetails() throws RemoteException { 374 bindName(); 375 bindGroup(); 376 } 377 378 private void bindName() { 379 final TextView channelName = findViewById(R.id.channel_name); 380 if (mIsSingleDefaultChannel || mNumUniqueChannelsInRow > 1) { 381 channelName.setVisibility(View.GONE); 382 } else { 383 channelName.setText(mSingleNotificationChannel.getName()); 384 } 385 } 386 387 private void bindDelegate() { 388 TextView delegateView = findViewById(R.id.delegate_name); 389 390 CharSequence delegatePkg = null; 391 if (!TextUtils.equals(mPackageName, mDelegatePkg)) { 392 // this notification was posted by a delegate! 393 delegateView.setVisibility(View.VISIBLE); 394 } else { 395 delegateView.setVisibility(View.GONE); 396 } 397 } 398 399 private void bindGroup() throws RemoteException { 400 // Set group information if this channel has an associated group. 401 CharSequence groupName = null; 402 if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) { 403 final NotificationChannelGroup notificationChannelGroup = 404 mINotificationManager.getNotificationChannelGroupForPackage( 405 mSingleNotificationChannel.getGroup(), mPackageName, mAppUid); 406 if (notificationChannelGroup != null) { 407 groupName = notificationChannelGroup.getName(); 408 } 409 } 410 TextView groupNameView = findViewById(R.id.group_name); 411 if (groupName != null) { 412 groupNameView.setText(groupName); 413 groupNameView.setVisibility(VISIBLE); 414 } else { 415 groupNameView.setVisibility(GONE); 416 } 417 } 418 419 private void saveImportance() { 420 if (!mIsNonblockable) { 421 if (mChosenImportance == null) { 422 mChosenImportance = mStartingChannelImportance; 423 } 424 updateImportance(); 425 } 426 } 427 428 /** 429 * Commits the updated importance values on the background thread. 430 */ 431 private void updateImportance() { 432 if (mChosenImportance != null) { 433 logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE); 434 mMetricsLogger.write(importanceChangeLogMaker()); 435 436 int newImportance = mChosenImportance; 437 if (mStartingChannelImportance != IMPORTANCE_UNSPECIFIED) { 438 if ((mWasShownHighPriority && mChosenImportance >= IMPORTANCE_DEFAULT) 439 || (!mWasShownHighPriority && mChosenImportance < IMPORTANCE_DEFAULT)) { 440 newImportance = mStartingChannelImportance; 441 } 442 } 443 444 Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER)); 445 bgHandler.post( 446 new UpdateImportanceRunnable(mINotificationManager, mPackageName, mAppUid, 447 mNumUniqueChannelsInRow == 1 ? mSingleNotificationChannel : null, 448 mStartingChannelImportance, newImportance, mIsAutomaticChosen)); 449 mOnUserInteractionCallback.onImportanceChanged(mEntry); 450 } 451 } 452 453 @Override 454 public boolean post(Runnable action) { 455 if (mSkipPost) { 456 action.run(); 457 return true; 458 } else { 459 return super.post(action); 460 } 461 } 462 463 private void applyAlertingBehavior(@AlertingBehavior int behavior, boolean userTriggered) { 464 if (userTriggered) { 465 TransitionSet transition = new TransitionSet(); 466 transition.setOrdering(TransitionSet.ORDERING_TOGETHER); 467 transition.addTransition(new Fade(Fade.OUT)) 468 .addTransition(new ChangeBounds()) 469 .addTransition( 470 new Fade(Fade.IN) 471 .setStartDelay(150) 472 .setDuration(200) 473 .setInterpolator(FAST_OUT_SLOW_IN)); 474 transition.setDuration(350); 475 transition.setInterpolator(FAST_OUT_SLOW_IN); 476 TransitionManager.beginDelayedTransition(this, transition); 477 } 478 479 View alert = findViewById(R.id.alert); 480 View silence = findViewById(R.id.silence); 481 View automatic = findViewById(R.id.automatic); 482 483 switch (behavior) { 484 case BEHAVIOR_ALERTING: 485 mPriorityDescriptionView.setVisibility(VISIBLE); 486 mSilentDescriptionView.setVisibility(GONE); 487 mAutomaticDescriptionView.setVisibility(GONE); 488 post(() -> { 489 alert.setSelected(true); 490 silence.setSelected(false); 491 automatic.setSelected(false); 492 }); 493 break; 494 495 case BEHAVIOR_SILENT: 496 mSilentDescriptionView.setVisibility(VISIBLE); 497 mPriorityDescriptionView.setVisibility(GONE); 498 mAutomaticDescriptionView.setVisibility(GONE); 499 post(() -> { 500 alert.setSelected(false); 501 silence.setSelected(true); 502 automatic.setSelected(false); 503 }); 504 break; 505 506 case BEHAVIOR_AUTOMATIC: 507 mAutomaticDescriptionView.setVisibility(VISIBLE); 508 mPriorityDescriptionView.setVisibility(GONE); 509 mSilentDescriptionView.setVisibility(GONE); 510 post(() -> { 511 automatic.setSelected(true); 512 alert.setSelected(false); 513 silence.setSelected(false); 514 }); 515 break; 516 517 default: 518 throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior); 519 } 520 521 boolean isAChange = getAlertingBehavior() != behavior; 522 TextView done = findViewById(R.id.done); 523 done.setText(isAChange 524 ? R.string.inline_ok_button 525 : R.string.inline_done_button); 526 } 527 528 @Override 529 public void onFinishedClosing() { 530 if (mChosenImportance != null) { 531 mStartingChannelImportance = mChosenImportance; 532 } 533 534 bindInlineControls(); 535 536 logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE); 537 mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE)); 538 } 539 540 @Override 541 public boolean needsFalsingProtection() { 542 return true; 543 } 544 545 @Override 546 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 547 super.onInitializeAccessibilityEvent(event); 548 if (mGutsContainer != null && 549 event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 550 if (mGutsContainer.isExposed()) { 551 event.getText().add(mContext.getString( 552 R.string.notification_channel_controls_opened_accessibility, mAppName)); 553 } else { 554 event.getText().add(mContext.getString( 555 R.string.notification_channel_controls_closed_accessibility, mAppName)); 556 } 557 } 558 } 559 560 private Intent getAppSettingsIntent(PackageManager pm, String packageName, 561 NotificationChannel channel, int id, String tag) { 562 Intent intent = new Intent(Intent.ACTION_MAIN) 563 .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES) 564 .setPackage(packageName); 565 final List<ResolveInfo> resolveInfos = pm.queryIntentActivities( 566 intent, 567 PackageManager.MATCH_DEFAULT_ONLY 568 ); 569 if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) { 570 return null; 571 } 572 final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo; 573 intent.setClassName(activityInfo.packageName, activityInfo.name); 574 if (channel != null) { 575 intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId()); 576 } 577 intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id); 578 intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag); 579 return intent; 580 } 581 582 @Override 583 public void setGutsParent(NotificationGuts guts) { 584 mGutsContainer = guts; 585 } 586 587 @Override 588 public boolean willBeRemoved() { 589 return false; 590 } 591 592 @Override 593 public boolean shouldBeSaved() { 594 return mPressedApply; 595 } 596 597 @Override 598 public View getContentView() { 599 return this; 600 } 601 602 @Override 603 public boolean handleCloseControls(boolean save, boolean force) { 604 if (mPresentingChannelEditorDialog && mChannelEditorDialogController != null) { 605 mPresentingChannelEditorDialog = false; 606 // No need for the finish listener because we're closing 607 mChannelEditorDialogController.setOnFinishListener(null); 608 mChannelEditorDialogController.close(); 609 } 610 611 // Save regardless of the importance so we can lock the importance field if the user wants 612 // to keep getting notifications 613 if (save) { 614 saveImportance(); 615 } 616 return false; 617 } 618 619 @Override 620 public int getActualHeight() { 621 // Because we're animating the bounds, getHeight will return the small height at the 622 // beginning of the animation. Instead we'd want it to already return the end value 623 return mActualHeight; 624 } 625 626 @Override 627 protected void onLayout(boolean changed, int l, int t, int r, int b) { 628 super.onLayout(changed, l, t, r, b); 629 mActualHeight = getHeight(); 630 } 631 632 @VisibleForTesting 633 public boolean isAnimating() { 634 return false; 635 } 636 637 /** 638 * Runnable to either update the given channel (with a new importance value) or, if no channel 639 * is provided, update notifications enabled state for the package. 640 */ 641 private static class UpdateImportanceRunnable implements Runnable { 642 private final INotificationManager mINotificationManager; 643 private final String mPackageName; 644 private final int mAppUid; 645 private final @Nullable NotificationChannel mChannelToUpdate; 646 private final int mCurrentImportance; 647 private final int mNewImportance; 648 private final boolean mUnlockImportance; 649 650 651 public UpdateImportanceRunnable(INotificationManager notificationManager, 652 String packageName, int appUid, @Nullable NotificationChannel channelToUpdate, 653 int currentImportance, int newImportance, boolean unlockImportance) { 654 mINotificationManager = notificationManager; 655 mPackageName = packageName; 656 mAppUid = appUid; 657 mChannelToUpdate = channelToUpdate; 658 mCurrentImportance = currentImportance; 659 mNewImportance = newImportance; 660 mUnlockImportance = unlockImportance; 661 } 662 663 @Override 664 public void run() { 665 try { 666 if (mChannelToUpdate != null) { 667 if (mUnlockImportance) { 668 mINotificationManager.unlockNotificationChannel( 669 mPackageName, mAppUid, mChannelToUpdate.getId()); 670 } else { 671 mChannelToUpdate.setImportance(mNewImportance); 672 mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); 673 mINotificationManager.updateNotificationChannelForPackage( 674 mPackageName, mAppUid, mChannelToUpdate); 675 } 676 } else { 677 // For notifications with more than one channel, update notification enabled 678 // state. If the importance was lowered, we disable notifications. 679 mINotificationManager.setNotificationsEnabledWithImportanceLockForPackage( 680 mPackageName, mAppUid, mNewImportance >= mCurrentImportance); 681 } 682 } catch (RemoteException e) { 683 Log.e(TAG, "Unable to update notification importance", e); 684 } 685 } 686 } 687 688 private void logUiEvent(NotificationControlsEvent event) { 689 if (mSbn != null) { 690 mUiEventLogger.logWithInstanceId(event, 691 mSbn.getUid(), mSbn.getPackageName(), mSbn.getInstanceId()); 692 } 693 } 694 695 /** 696 * Returns a LogMaker with all available notification information. 697 * Caller should set category, type, and maybe subtype, before passing it to mMetricsLogger. 698 * @return LogMaker 699 */ 700 private LogMaker getLogMaker() { 701 // The constructor requires a category, so also do it in the other branch for consistency. 702 return mSbn == null ? new LogMaker(MetricsEvent.NOTIFICATION_BLOCKING_HELPER) 703 : mSbn.getLogMaker().setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER); 704 } 705 706 /** 707 * Returns an initialized LogMaker for logging importance changes. 708 * The caller may override the type before passing it to mMetricsLogger. 709 * @return LogMaker 710 */ 711 private LogMaker importanceChangeLogMaker() { 712 Integer chosenImportance = 713 mChosenImportance != null ? mChosenImportance : mStartingChannelImportance; 714 return getLogMaker().setCategory(MetricsEvent.ACTION_SAVE_IMPORTANCE) 715 .setType(MetricsEvent.TYPE_ACTION) 716 .setSubtype(chosenImportance - mStartingChannelImportance); 717 } 718 719 /** 720 * Returns an initialized LogMaker for logging open/close of the info display. 721 * The caller may override the type before passing it to mMetricsLogger. 722 * @return LogMaker 723 */ 724 private LogMaker notificationControlsLogMaker() { 725 return getLogMaker().setCategory(MetricsEvent.ACTION_NOTE_CONTROLS) 726 .setType(MetricsEvent.TYPE_OPEN) 727 .setSubtype(MetricsEvent.BLOCKING_HELPER_UNKNOWN); 728 } 729 730 private @AlertingBehavior int getAlertingBehavior() { 731 if (mShowAutomaticSetting && !mSingleNotificationChannel.hasUserSetImportance()) { 732 return BEHAVIOR_AUTOMATIC; 733 } 734 return mWasShownHighPriority ? BEHAVIOR_ALERTING : BEHAVIOR_SILENT; 735 } 736 737 @Retention(SOURCE) 738 @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_AUTOMATIC}) 739 private @interface AlertingBehavior {} 740 private static final int BEHAVIOR_ALERTING = 0; 741 private static final int BEHAVIOR_SILENT = 1; 742 private static final int BEHAVIOR_AUTOMATIC = 2; 743 } 744