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