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