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