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