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