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 package com.android.systemui.statusbar.notification.row; 17 18 import static android.app.AppOpsManager.OP_CAMERA; 19 import static android.app.AppOpsManager.OP_RECORD_AUDIO; 20 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; 21 22 import android.app.INotificationManager; 23 import android.app.NotificationChannel; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.LauncherApps; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ShortcutManager; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.provider.Settings; 36 import android.service.notification.NotificationListenerService; 37 import android.service.notification.StatusBarNotification; 38 import android.util.ArraySet; 39 import android.util.IconDrawableFactory; 40 import android.util.Log; 41 import android.view.HapticFeedbackConstants; 42 import android.view.View; 43 import android.view.accessibility.AccessibilityManager; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.logging.MetricsLogger; 47 import com.android.internal.logging.UiEventLogger; 48 import com.android.internal.logging.nano.MetricsProto; 49 import com.android.internal.statusbar.IStatusBarService; 50 import com.android.settingslib.notification.ConversationIconFactory; 51 import com.android.systemui.CoreStartable; 52 import com.android.systemui.dagger.SysUISingleton; 53 import com.android.systemui.dagger.qualifiers.Background; 54 import com.android.systemui.dagger.qualifiers.Main; 55 import com.android.systemui.people.widget.PeopleSpaceWidgetManager; 56 import com.android.systemui.plugins.ActivityStarter; 57 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 58 import com.android.systemui.plugins.statusbar.StatusBarStateController; 59 import com.android.systemui.res.R; 60 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; 61 import com.android.systemui.settings.UserContextProvider; 62 import com.android.systemui.shade.ShadeController; 63 import com.android.systemui.shade.ShadeDisplayAware; 64 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 65 import com.android.systemui.statusbar.NotificationPresenter; 66 import com.android.systemui.statusbar.StatusBarState; 67 import com.android.systemui.statusbar.StatusBarStateControllerImpl; 68 import com.android.systemui.statusbar.notification.AssistantFeedbackController; 69 import com.android.systemui.statusbar.notification.NotificationActivityStarter; 70 import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; 71 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewListener; 72 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; 73 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; 74 import com.android.systemui.statusbar.notification.promoted.domain.interactor.PackageDemotionInteractor; 75 import com.android.systemui.statusbar.notification.row.icon.AppIconProvider; 76 import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider; 77 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi; 78 import com.android.systemui.statusbar.notification.stack.NotificationListContainer; 79 import com.android.systemui.statusbar.phone.CentralSurfaces; 80 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 81 import com.android.systemui.util.kotlin.JavaAdapter; 82 import com.android.systemui.wmshell.BubblesManager; 83 84 import java.util.Optional; 85 86 import javax.inject.Inject; 87 88 /** 89 * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and 90 * closing guts, and keeping track of the currently exposed notification guts. 91 */ 92 @SysUISingleton 93 public class NotificationGutsManager implements NotifGutsViewManager, CoreStartable { 94 private static final String TAG = "NotificationGutsManager"; 95 96 // Must match constant in Settings. Used to highlight preferences when linking to Settings. 97 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 98 99 private final MetricsLogger mMetricsLogger; 100 private final Context mContext; 101 private final AccessibilityManager mAccessibilityManager; 102 private final HighPriorityProvider mHighPriorityProvider; 103 private final ChannelEditorDialogController mChannelEditorDialogController; 104 private final PackageDemotionInteractor mPackageDemotionInteractor; 105 private final OnUserInteractionCallback mOnUserInteractionCallback; 106 107 // Dependencies: 108 private final NotificationLockscreenUserManager mLockscreenUserManager; 109 private final StatusBarStateController mStatusBarStateController; 110 private final IStatusBarService mStatusBarService; 111 private final DeviceProvisionedController mDeviceProvisionedController; 112 private final AssistantFeedbackController mAssistantFeedbackController; 113 114 // which notification is currently being longpress-examined by the user 115 private NotificationGuts mNotificationGutsExposed; 116 private NotificationMenuRowPlugin.MenuItem mGutsMenuItem; 117 private NotificationPresenter mPresenter; 118 private NotificationActivityStarter mNotificationActivityStarter; 119 private NotificationListContainer mListContainer; 120 private OnSettingsClickListener mOnSettingsClickListener; 121 122 private final Handler mMainHandler; 123 private final Handler mBgHandler; 124 private final JavaAdapter mJavaAdapter; 125 private final Optional<BubblesManager> mBubblesManagerOptional; 126 private Runnable mOpenRunnable; 127 private final INotificationManager mNotificationManager; 128 private final AppIconProvider mAppIconProvider; 129 private final NotificationIconStyleProvider mIconStyleProvider; 130 private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager; 131 132 private final UserManager mUserManager; 133 134 private final LauncherApps mLauncherApps; 135 private final ShortcutManager mShortcutManager; 136 private final UserContextProvider mContextTracker; 137 private final UiEventLogger mUiEventLogger; 138 private final ShadeController mShadeController; 139 private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; 140 private NotifGutsViewListener mGutsListener; 141 private final HeadsUpManager mHeadsUpManager; 142 private final ActivityStarter mActivityStarter; 143 144 @Inject NotificationGutsManager( @hadeDisplayAware Context context, @Main Handler mainHandler, @Background Handler bgHandler, JavaAdapter javaAdapter, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, AppIconProvider appIconProvider, NotificationIconStyleProvider iconStyleProvider, UserManager userManager, PeopleSpaceWidgetManager peopleSpaceWidgetManager, LauncherApps launcherApps, ShortcutManager shortcutManager, ChannelEditorDialogController channelEditorDialogController, PackageDemotionInteractor packageDemotionInteractor, UserContextProvider contextTracker, AssistantFeedbackController assistantFeedbackController, Optional<BubblesManager> bubblesManagerOptional, UiEventLogger uiEventLogger, OnUserInteractionCallback onUserInteractionCallback, ShadeController shadeController, WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, NotificationLockscreenUserManager notificationLockscreenUserManager, StatusBarStateController statusBarStateController, IStatusBarService statusBarService, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, HeadsUpManager headsUpManager, ActivityStarter activityStarter)145 public NotificationGutsManager( 146 @ShadeDisplayAware Context context, 147 @Main Handler mainHandler, 148 @Background Handler bgHandler, 149 JavaAdapter javaAdapter, 150 AccessibilityManager accessibilityManager, 151 HighPriorityProvider highPriorityProvider, 152 INotificationManager notificationManager, 153 AppIconProvider appIconProvider, 154 NotificationIconStyleProvider iconStyleProvider, 155 UserManager userManager, 156 PeopleSpaceWidgetManager peopleSpaceWidgetManager, 157 LauncherApps launcherApps, 158 ShortcutManager shortcutManager, 159 ChannelEditorDialogController channelEditorDialogController, 160 PackageDemotionInteractor packageDemotionInteractor, 161 UserContextProvider contextTracker, 162 AssistantFeedbackController assistantFeedbackController, 163 Optional<BubblesManager> bubblesManagerOptional, 164 UiEventLogger uiEventLogger, 165 OnUserInteractionCallback onUserInteractionCallback, 166 ShadeController shadeController, 167 WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor, 168 NotificationLockscreenUserManager notificationLockscreenUserManager, 169 StatusBarStateController statusBarStateController, 170 IStatusBarService statusBarService, 171 DeviceProvisionedController deviceProvisionedController, 172 MetricsLogger metricsLogger, 173 HeadsUpManager headsUpManager, 174 ActivityStarter activityStarter) { 175 mContext = context; 176 mMainHandler = mainHandler; 177 mBgHandler = bgHandler; 178 mJavaAdapter = javaAdapter; 179 mAccessibilityManager = accessibilityManager; 180 mHighPriorityProvider = highPriorityProvider; 181 mNotificationManager = notificationManager; 182 mAppIconProvider = appIconProvider; 183 mIconStyleProvider = iconStyleProvider; 184 mUserManager = userManager; 185 mPeopleSpaceWidgetManager = peopleSpaceWidgetManager; 186 mLauncherApps = launcherApps; 187 mShortcutManager = shortcutManager; 188 mContextTracker = contextTracker; 189 mChannelEditorDialogController = channelEditorDialogController; 190 mPackageDemotionInteractor = packageDemotionInteractor; 191 mAssistantFeedbackController = assistantFeedbackController; 192 mBubblesManagerOptional = bubblesManagerOptional; 193 mUiEventLogger = uiEventLogger; 194 mOnUserInteractionCallback = onUserInteractionCallback; 195 mShadeController = shadeController; 196 mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor; 197 mLockscreenUserManager = notificationLockscreenUserManager; 198 mStatusBarStateController = statusBarStateController; 199 mStatusBarService = statusBarService; 200 mDeviceProvisionedController = deviceProvisionedController; 201 mMetricsLogger = metricsLogger; 202 mHeadsUpManager = headsUpManager; 203 mActivityStarter = activityStarter; 204 } 205 setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, OnSettingsClickListener onSettingsClick)206 public void setUpWithPresenter(NotificationPresenter presenter, 207 NotificationListContainer listContainer, 208 OnSettingsClickListener onSettingsClick) { 209 mPresenter = presenter; 210 mListContainer = listContainer; 211 mOnSettingsClickListener = onSettingsClick; 212 } 213 setNotificationActivityStarter( NotificationActivityStarter notificationActivityStarter)214 public void setNotificationActivityStarter( 215 NotificationActivityStarter notificationActivityStarter) { 216 mNotificationActivityStarter = notificationActivityStarter; 217 } 218 219 @Override start()220 public void start() { 221 mJavaAdapter.alwaysCollectFlow( 222 mWindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible(), 223 this::onLockscreenShadeVisibilityChanged); 224 } 225 onLockscreenShadeVisibilityChanged(boolean visible)226 private void onLockscreenShadeVisibilityChanged(boolean visible) { 227 if (!visible) { 228 closeAndSaveGuts( 229 /* removeLeavebehind= */ true , 230 /* force= */ true, 231 /* removeControls= */ true, 232 /* x= */ -1, 233 /* y= */ -1, 234 /* resetMenu= */ true); 235 } 236 } 237 238 /** 239 * Sends an intent to open the notification settings for a particular package and optional 240 * channel. 241 */ 242 public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; startAppNotificationSettingsActivity(String packageName, final int appUid, final NotificationChannel channel, ExpandableNotificationRow row)243 private void startAppNotificationSettingsActivity(String packageName, final int appUid, 244 final NotificationChannel channel, ExpandableNotificationRow row) { 245 final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 246 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 247 intent.putExtra(Settings.EXTRA_APP_UID, appUid); 248 249 if (channel != null) { 250 final Bundle args = new Bundle(); 251 intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); 252 args.putString(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); 253 intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); 254 } 255 mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row); 256 } 257 startAppDetailsSettingsActivity(String packageName, final int appUid, ExpandableNotificationRow row)258 private void startAppDetailsSettingsActivity(String packageName, final int appUid, 259 ExpandableNotificationRow row) { 260 final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 261 intent.setData(Uri.fromParts("package", packageName, null)); 262 intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); 263 intent.putExtra(Settings.EXTRA_APP_UID, appUid); 264 mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row); 265 } 266 startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops, ExpandableNotificationRow row)267 protected void startAppOpsSettingsActivity(String pkg, int uid, ArraySet<Integer> ops, 268 ExpandableNotificationRow row) { 269 if (ops.contains(OP_SYSTEM_ALERT_WINDOW)) { 270 if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) { 271 startAppDetailsSettingsActivity(pkg, uid, row); 272 } else { 273 Intent intent = new Intent(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION); 274 intent.setData(Uri.fromParts("package", pkg, null)); 275 mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row); 276 } 277 } else if (ops.contains(OP_CAMERA) || ops.contains(OP_RECORD_AUDIO)) { 278 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS); 279 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, pkg); 280 mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row); 281 } 282 } 283 startConversationSettingsActivity(int uid, ExpandableNotificationRow row)284 private void startConversationSettingsActivity(int uid, ExpandableNotificationRow row) { 285 final Intent intent = new Intent(Settings.ACTION_CONVERSATION_SETTINGS); 286 mNotificationActivityStarter.startNotificationGutsIntent(intent, uid, row); 287 } 288 289 @VisibleForTesting bindGuts(final ExpandableNotificationRow row, NotificationMenuRowPlugin.MenuItem item)290 protected boolean bindGuts(final ExpandableNotificationRow row, 291 NotificationMenuRowPlugin.MenuItem item) { 292 293 StatusBarNotification sbn = NotificationBundleUi.isEnabled() 294 ? row.getEntryAdapter().getSbn() 295 : row.getEntryLegacy().getSbn(); 296 NotificationListenerService.Ranking ranking = NotificationBundleUi.isEnabled() 297 ? row.getEntryAdapter().getRanking() 298 : row.getEntryLegacy().getRanking(); 299 300 if (sbn == null || ranking == null) { 301 // only valid for notification rows 302 return false; 303 } 304 305 row.setGutsView(item); 306 row.setTag(sbn.getPackageName()); 307 row.getGuts().setClosedListener((NotificationGuts g) -> { 308 row.onGutsClosed(); 309 if (!g.willBeRemoved() && !row.isRemoved()) { 310 mListContainer.onHeightChanged( 311 row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */); 312 } 313 if (mNotificationGutsExposed == g) { 314 mNotificationGutsExposed = null; 315 mGutsMenuItem = null; 316 } 317 if (mGutsListener != null) { 318 if (NotificationBundleUi.isEnabled()) { 319 mGutsListener.onGutsClose(row.getEntryAdapter()); 320 row.updateBubbleButton(); 321 } else { 322 mGutsListener.onGutsClose(row.getEntryLegacy()); 323 } 324 } 325 if(NotificationBundleUi.isEnabled()) { 326 row.getEntryAdapter().setInlineControlsShown(false); 327 } else { 328 mHeadsUpManager.setGutsShown(row.getEntryLegacy(), false); 329 } 330 }); 331 332 View gutsView = item.getGutsView(); 333 334 try { 335 if (gutsView instanceof NotificationSnooze) { 336 initializeSnoozeView(row, sbn, ranking, (NotificationSnooze) gutsView); 337 } else if (gutsView instanceof NotificationInfo) { 338 initializeNotificationInfo(row, sbn, ranking, (NotificationInfo) gutsView); 339 } else if (gutsView instanceof NotificationConversationInfo) { 340 initializeConversationNotificationInfo( 341 row, sbn, ranking, (NotificationConversationInfo) gutsView); 342 } else if (gutsView instanceof PartialConversationInfo) { 343 initializePartialConversationNotificationInfo(row, sbn, ranking, 344 (PartialConversationInfo) gutsView); 345 } else if (gutsView instanceof FeedbackInfo) { 346 initializeFeedbackInfo(row, sbn, ranking, (FeedbackInfo) gutsView); 347 } else if (gutsView instanceof PromotedPermissionGutsContent) { 348 initializeDemoteView(row, sbn, (PromotedPermissionGutsContent) gutsView); 349 } 350 return true; 351 } catch (Exception e) { 352 Log.e(TAG, "error binding guts", e); 353 return false; 354 } 355 } 356 357 /** 358 * Sets up the {@link NotificationSnooze} inside the notification row's guts. 359 * 360 * @param row view to set up the guts for 361 * @param notificationSnoozeView view to set up/bind within {@code row} 362 */ initializeSnoozeView( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationSnooze notificationSnoozeView)363 private void initializeSnoozeView( 364 final ExpandableNotificationRow row, 365 final StatusBarNotification sbn, 366 final NotificationListenerService.Ranking ranking, 367 NotificationSnooze notificationSnoozeView) { 368 NotificationGuts guts = row.getGuts(); 369 370 notificationSnoozeView.setSnoozeListener(mListContainer.getSwipeActionHelper()); 371 notificationSnoozeView.setStatusBarNotification(sbn); 372 notificationSnoozeView.setSnoozeOptions(ranking.getSnoozeCriteria()); 373 guts.setHeightChangedListener((NotificationGuts g) -> { 374 mListContainer.onHeightChanged(row, row.isShown() /* needsAnimation */); 375 }); 376 } 377 378 /** 379 * Sets up the {@link NotificationSnooze} inside the notification row's guts. 380 * 381 * @param row view to set up the guts for 382 * @param demoteGuts view to set up/bind within {@code row} 383 */ initializeDemoteView( final ExpandableNotificationRow row, StatusBarNotification sbn, PromotedPermissionGutsContent demoteGuts)384 private void initializeDemoteView( 385 final ExpandableNotificationRow row, 386 StatusBarNotification sbn, 387 PromotedPermissionGutsContent demoteGuts) { 388 demoteGuts.setStatusBarNotification(sbn); 389 demoteGuts.setOnDemoteAction(new View.OnClickListener() { 390 @Override 391 public void onClick(View v) { 392 try { 393 // TODO(b/391661009): Signal AutomaticPromotionCoordinator here 394 mNotificationManager.setCanBePromoted( 395 sbn.getPackageName(), sbn.getUid(), false, true); 396 } catch (RemoteException e) { 397 Log.e(TAG, "Couldn't revoke live update permission", e); 398 } 399 } 400 }); 401 } 402 403 /** 404 * Sets up the {@link FeedbackInfo} inside the notification row's guts. 405 * 406 * @param row view to set up the guts for 407 * @param feedbackInfo view to set up/bind within {@code row} 408 */ initializeFeedbackInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, FeedbackInfo feedbackInfo)409 private void initializeFeedbackInfo( 410 final ExpandableNotificationRow row, 411 final StatusBarNotification sbn, 412 final NotificationListenerService.Ranking ranking, 413 FeedbackInfo feedbackInfo) { 414 if (mAssistantFeedbackController.getFeedbackIcon(ranking) == null) { 415 return; 416 } 417 UserHandle userHandle = sbn.getUser(); 418 PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(mContext, 419 userHandle.getIdentifier()); 420 421 feedbackInfo.bindGuts(pmUser, sbn, ranking, row, mAssistantFeedbackController, 422 mStatusBarService, this); 423 } 424 425 /** 426 * Sets up the {@link NotificationInfo} inside the notification row's guts. 427 * @param row view to set up the guts for 428 * @param notificationInfoView view to set up/bind within {@code row} 429 */ 430 @VisibleForTesting initializeNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationInfo notificationInfoView)431 void initializeNotificationInfo( 432 final ExpandableNotificationRow row, 433 final StatusBarNotification sbn, 434 final NotificationListenerService.Ranking ranking, 435 NotificationInfo notificationInfoView) throws Exception { 436 NotificationGuts guts = row.getGuts(); 437 String packageName = sbn.getPackageName(); 438 // Settings link is only valid for notifications that specify a non-system user 439 NotificationInfo.OnSettingsClickListener onSettingsClick = null; 440 UserHandle userHandle = sbn.getUser(); 441 PackageManager pmUser = CentralSurfaces.getPackageManagerForUser( 442 mContext, userHandle.getIdentifier()); 443 final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = 444 (View v, Intent intent) -> { 445 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS); 446 guts.resetFalsingCheck(); 447 mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), 448 row); 449 }; 450 451 if (!userHandle.equals(UserHandle.ALL) 452 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { 453 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { 454 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); 455 guts.resetFalsingCheck(); 456 mOnSettingsClickListener.onSettingsClick(sbn.getKey()); 457 startAppNotificationSettingsActivity(packageName, appUid, channel, row); 458 }; 459 } 460 461 NotificationInfo.OnFeedbackClickListener onNasFeedbackClick = (View v, Intent intent) -> { 462 guts.resetFalsingCheck(); 463 mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), row); 464 }; 465 466 notificationInfoView.bindNotification( 467 pmUser, 468 mNotificationManager, 469 mAppIconProvider, 470 mIconStyleProvider, 471 mOnUserInteractionCallback, 472 mChannelEditorDialogController, 473 mPackageDemotionInteractor, 474 packageName, 475 ranking, 476 sbn, 477 NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(), 478 NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null, 479 onSettingsClick, 480 onAppSettingsClick, 481 onNasFeedbackClick, 482 mUiEventLogger, 483 mDeviceProvisionedController.isDeviceProvisioned(), 484 NotificationBundleUi.isEnabled() 485 ? !row.getEntryAdapter().isBlockable() 486 : row.getIsNonblockable(), 487 row.canViewBeDismissed(), 488 NotificationBundleUi.isEnabled() 489 ? row.getEntryAdapter().isHighPriority() 490 : mHighPriorityProvider.isHighPriority(row.getEntryLegacy()), 491 mAssistantFeedbackController, 492 mMetricsLogger, 493 row.getCloseButtonOnClickListener(row)); 494 } 495 496 /** 497 * Sets up the {@link PartialConversationInfo} inside the notification row's guts. 498 * @param row view to set up the guts for 499 * @param notificationInfoView view to set up/bind within {@code row} 500 */ 501 @VisibleForTesting initializePartialConversationNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, PartialConversationInfo notificationInfoView)502 void initializePartialConversationNotificationInfo( 503 final ExpandableNotificationRow row, 504 final StatusBarNotification sbn, 505 final NotificationListenerService.Ranking ranking, 506 PartialConversationInfo notificationInfoView) throws Exception { 507 NotificationGuts guts = row.getGuts(); 508 String packageName = sbn.getPackageName(); 509 // Settings link is only valid for notifications that specify a non-system user 510 NotificationInfo.OnSettingsClickListener onSettingsClick = null; 511 UserHandle userHandle = sbn.getUser(); 512 PackageManager pmUser = CentralSurfaces.getPackageManagerForUser( 513 mContext, userHandle.getIdentifier()); 514 515 if (!userHandle.equals(UserHandle.ALL) 516 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { 517 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { 518 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); 519 guts.resetFalsingCheck(); 520 mOnSettingsClickListener.onSettingsClick(sbn.getKey()); 521 startAppNotificationSettingsActivity(packageName, appUid, channel, row); 522 }; 523 } 524 525 notificationInfoView.bindNotification( 526 pmUser, 527 mNotificationManager, 528 mChannelEditorDialogController, 529 packageName, 530 ranking, 531 sbn, 532 onSettingsClick, 533 mDeviceProvisionedController.isDeviceProvisioned(), 534 NotificationBundleUi.isEnabled() 535 ? !row.getEntryAdapter().isBlockable() 536 : row.getIsNonblockable()); 537 } 538 539 /** 540 * Sets up the {@link NotificationConversationInfo} inside the notification row's guts. 541 * @param row view to set up the guts for 542 * @param notificationInfoView view to set up/bind within {@code row} 543 */ 544 @VisibleForTesting initializeConversationNotificationInfo( final ExpandableNotificationRow row, final StatusBarNotification sbn, final NotificationListenerService.Ranking ranking, NotificationConversationInfo notificationInfoView)545 void initializeConversationNotificationInfo( 546 final ExpandableNotificationRow row, 547 final StatusBarNotification sbn, 548 final NotificationListenerService.Ranking ranking, 549 NotificationConversationInfo notificationInfoView) throws Exception { 550 NotificationGuts guts = row.getGuts(); 551 String packageName = sbn.getPackageName(); 552 // Settings link is only valid for notifications that specify a non-system user 553 NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null; 554 UserHandle userHandle = sbn.getUser(); 555 PackageManager pmUser = CentralSurfaces.getPackageManagerForUser( 556 mContext, userHandle.getIdentifier()); 557 final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick = 558 (View v, Intent intent) -> { 559 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS); 560 guts.resetFalsingCheck(); 561 mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), 562 row); 563 }; 564 565 final NotificationConversationInfo.OnConversationSettingsClickListener 566 onConversationSettingsListener = 567 () -> { 568 startConversationSettingsActivity(sbn.getUid(), row); 569 }; 570 571 if (!userHandle.equals(UserHandle.ALL) 572 || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) { 573 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> { 574 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO); 575 guts.resetFalsingCheck(); 576 mOnSettingsClickListener.onSettingsClick(sbn.getKey()); 577 startAppNotificationSettingsActivity(packageName, appUid, channel, row); 578 }; 579 } 580 ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext, 581 mLauncherApps, pmUser, IconDrawableFactory.newInstance(mContext, false), 582 mContext.getResources().getDimensionPixelSize( 583 R.dimen.notification_guts_conversation_icon_size)); 584 585 NotificationInfo.OnFeedbackClickListener onNasFeedbackClick = (View v, Intent intent) -> { 586 guts.resetFalsingCheck(); 587 mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(), row); 588 }; 589 590 notificationInfoView.bindNotification( 591 mShortcutManager, 592 pmUser, 593 mUserManager, 594 mPeopleSpaceWidgetManager, 595 mNotificationManager, 596 mOnUserInteractionCallback, 597 packageName, 598 NotificationBundleUi.isEnabled() ? null : row.getEntryLegacy(), 599 NotificationBundleUi.isEnabled() ? row.getEntryAdapter() : null, 600 ranking, 601 sbn, 602 onSettingsClick, 603 onNasFeedbackClick, 604 iconFactoryLoader, 605 mContextTracker.getUserContext(), 606 mDeviceProvisionedController.isDeviceProvisioned(), 607 mMainHandler, 608 mBgHandler, 609 onConversationSettingsListener, 610 mBubblesManagerOptional, 611 mShadeController, 612 row.canViewBeDismissed(), 613 row.getCloseButtonOnClickListener(row)); 614 } 615 616 /** 617 * Closes guts or notification menus that might be visible and saves any changes if applicable 618 * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}). 619 * 620 * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. 621 * @param force true if guts should be closed regardless of state (used for snooze only). 622 * @param removeControls true if controls (e.g. info) should be closed. 623 * @param x if closed based on touch location, this is the x touch location. 624 * @param y if closed based on touch location, this is the y touch location. 625 * @param resetMenu if any notification menus that might be revealed should be closed. 626 */ closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, int x, int y, boolean resetMenu)627 public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls, 628 int x, int y, boolean resetMenu) { 629 if (mNotificationGutsExposed != null) { 630 mNotificationGutsExposed.removeCallbacks(mOpenRunnable); 631 mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force); 632 } 633 if (resetMenu && mListContainer != null) { 634 mListContainer.resetExposedMenuView(false /* animate */, true /* force */); 635 } 636 } 637 638 /** 639 * Closes all guts that might be visible without saving changes. 640 */ closeAndUndoGuts()641 public void closeAndUndoGuts() { 642 if (mNotificationGutsExposed != null) { 643 mNotificationGutsExposed.removeCallbacks(mOpenRunnable); 644 mNotificationGutsExposed.closeControls( 645 /* x = */ -1, 646 /* y = */ -1, 647 /* save = */ false, 648 /* force = */ false); 649 } 650 } 651 652 /** 653 * Returns the exposed NotificationGuts or null if none are exposed. 654 */ getExposedGuts()655 public NotificationGuts getExposedGuts() { 656 return mNotificationGutsExposed; 657 } 658 659 @VisibleForTesting setExposedGuts(NotificationGuts guts)660 public void setExposedGuts(NotificationGuts guts) { 661 mNotificationGutsExposed = guts; 662 } 663 getNotificationLongClicker()664 public ExpandableNotificationRow.LongPressListener getNotificationLongClicker() { 665 return this::openGuts; 666 } 667 668 /** 669 * Opens guts on the given ExpandableNotificationRow {@code view}. This handles opening guts for 670 * the normal half-swipe and long-press use cases via a circular reveal. When the blocking 671 * helper needs to be shown on the row, this will skip the circular reveal. 672 * 673 * @param view ExpandableNotificationRow to open guts on 674 * @param x x coordinate of origin of circular reveal 675 * @param y y coordinate of origin of circular reveal 676 * @param menuItem MenuItem the guts should display 677 * @return true if guts was opened 678 */ openGuts( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)679 public boolean openGuts( 680 View view, 681 int x, 682 int y, 683 NotificationMenuRowPlugin.MenuItem menuItem) { 684 if (menuItem.getGutsView() instanceof NotificationGuts.GutsContent) { 685 NotificationGuts.GutsContent gutsView = 686 (NotificationGuts.GutsContent) menuItem.getGutsView(); 687 if (gutsView.needsFalsingProtection()) { 688 if (mStatusBarStateController instanceof StatusBarStateControllerImpl) { 689 ((StatusBarStateControllerImpl) mStatusBarStateController) 690 .setLeaveOpenOnKeyguardHide(true); 691 } 692 693 Runnable r = () -> mMainHandler.post( 694 () -> openGutsInternal(view, x, y, menuItem)); 695 // If the bouncer shows, it will block the TOUCH_UP event from reaching the notif, 696 // so explicitly mark it as unpressed here to reset the touch animation. 697 view.setPressed(false); 698 mActivityStarter.executeRunnableDismissingKeyguard( 699 r, 700 null /* cancelAction */, 701 false /* dismissShade */, 702 true /* afterKeyguardGone */, 703 true /* deferred */); 704 return true; 705 /** 706 * When {@link CentralSurfaces} doesn't exist, falling through to call 707 * {@link #openGutsInternal(View,int,int,NotificationMenuRowPlugin.MenuItem)}. 708 */ 709 } 710 } 711 return openGutsInternal(view, x, y, menuItem); 712 } 713 714 @VisibleForTesting openGutsInternal( View view, int x, int y, NotificationMenuRowPlugin.MenuItem menuItem)715 boolean openGutsInternal( 716 View view, 717 int x, 718 int y, 719 NotificationMenuRowPlugin.MenuItem menuItem) { 720 721 if (!(view instanceof ExpandableNotificationRow)) { 722 return false; 723 } 724 725 if (view.getWindowToken() == null) { 726 Log.e(TAG, "Trying to show notification guts, but not attached to window"); 727 return false; 728 } 729 730 final ExpandableNotificationRow row = (ExpandableNotificationRow) view; 731 if (row.isNotificationRowLongClickable()) { 732 view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 733 } 734 if (row.areGutsExposed()) { 735 closeAndSaveGuts(false /* removeLeavebehind */, false /* force */, 736 true /* removeControls */, -1 /* x */, -1 /* y */, 737 true /* resetMenu */); 738 return false; 739 } 740 741 row.ensureGutsInflated(); 742 NotificationGuts guts = row.getGuts(); 743 mNotificationGutsExposed = guts; 744 if (!bindGuts(row, menuItem)) { 745 // exception occurred trying to fill in all the data, bail. 746 return false; 747 } 748 749 750 // Assume we are a status_bar_notification_row 751 if (guts == null) { 752 // This view has no guts. Examples are the more card or the dismiss all view 753 return false; 754 } 755 756 // ensure that it's laid but not visible until actually laid out 757 guts.setVisibility(View.INVISIBLE); 758 // Post to ensure the the guts are properly laid out. 759 mOpenRunnable = new Runnable() { 760 @Override 761 public void run() { 762 if (row.getWindowToken() == null) { 763 Log.e(TAG, "Trying to show notification guts in post(), but not attached to " 764 + "window"); 765 return; 766 } 767 guts.setVisibility(View.VISIBLE); 768 769 final boolean needsFalsingProtection = 770 (mStatusBarStateController.getState() == StatusBarState.KEYGUARD && 771 !mAccessibilityManager.isTouchExplorationEnabled()); 772 773 guts.openControls( 774 x, 775 y, 776 needsFalsingProtection, 777 row::onGutsOpened); 778 779 if (mGutsListener != null) { 780 if(NotificationBundleUi.isEnabled()) { 781 mGutsListener.onGutsOpen(row.getEntryAdapter(), guts); 782 } else { 783 mGutsListener.onGutsOpen(row.getEntryLegacy(), guts); 784 } 785 } 786 787 row.closeRemoteInput(); 788 mListContainer.onHeightChanged(row, true /* needsAnimation */); 789 mGutsMenuItem = menuItem; 790 if(NotificationBundleUi.isEnabled()) { 791 row.getEntryAdapter().setInlineControlsShown(true); 792 } else { 793 mHeadsUpManager.setGutsShown(row.getEntryLegacy(), true); 794 } 795 } 796 }; 797 guts.post(mOpenRunnable); 798 return true; 799 } 800 801 /** 802 * @param gutsListener the listener for open and close guts events 803 */ setGutsListener(NotifGutsViewListener gutsListener)804 public void setGutsListener(NotifGutsViewListener gutsListener) { 805 mGutsListener = gutsListener; 806 } 807 808 public interface OnSettingsClickListener { onSettingsClick(String key)809 public void onSettingsClick(String key); 810 } 811 } 812