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