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