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