1 /* 2 * Copyright (C) 2018 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 17 package com.android.systemui.statusbar.phone; 18 19 import static android.service.notification.NotificationListenerService.REASON_CLICK; 20 21 import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions; 22 23 import android.app.ActivityManager; 24 import android.app.ActivityTaskManager; 25 import android.app.KeyguardManager; 26 import android.app.Notification; 27 import android.app.NotificationManager; 28 import android.app.PendingIntent; 29 import android.app.TaskStackBuilder; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.os.AsyncTask; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.RemoteException; 36 import android.os.UserHandle; 37 import android.service.dreams.IDreamManager; 38 import android.service.notification.StatusBarNotification; 39 import android.text.TextUtils; 40 import android.util.EventLog; 41 import android.util.Log; 42 import android.view.RemoteAnimationAdapter; 43 44 import com.android.internal.logging.MetricsLogger; 45 import com.android.internal.statusbar.IStatusBarService; 46 import com.android.internal.statusbar.NotificationVisibility; 47 import com.android.internal.widget.LockPatternUtils; 48 import com.android.systemui.ActivityIntentHelper; 49 import com.android.systemui.Dependency; 50 import com.android.systemui.EventLogTags; 51 import com.android.systemui.UiOffloadThread; 52 import com.android.systemui.assist.AssistManager; 53 import com.android.systemui.bubbles.BubbleController; 54 import com.android.systemui.plugins.ActivityStarter; 55 import com.android.systemui.plugins.statusbar.StatusBarStateController; 56 import com.android.systemui.statusbar.CommandQueue; 57 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 58 import com.android.systemui.statusbar.NotificationPresenter; 59 import com.android.systemui.statusbar.NotificationRemoteInputManager; 60 import com.android.systemui.statusbar.RemoteInputController; 61 import com.android.systemui.statusbar.StatusBarState; 62 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; 63 import com.android.systemui.statusbar.notification.NotificationActivityStarter; 64 import com.android.systemui.statusbar.notification.NotificationEntryListener; 65 import com.android.systemui.statusbar.notification.NotificationEntryManager; 66 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; 67 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 68 import com.android.systemui.statusbar.notification.logging.NotificationLogger; 69 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 70 import com.android.systemui.statusbar.policy.HeadsUpUtil; 71 import com.android.systemui.statusbar.policy.KeyguardMonitor; 72 73 /** 74 * Status bar implementation of {@link NotificationActivityStarter}. 75 */ 76 public class StatusBarNotificationActivityStarter implements NotificationActivityStarter { 77 78 private static final String TAG = "NotificationClickHandler"; 79 protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 80 81 private final AssistManager mAssistManager; 82 private final NotificationGroupManager mGroupManager; 83 private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback; 84 private final NotificationRemoteInputManager mRemoteInputManager; 85 private final NotificationLockscreenUserManager mLockscreenUserManager; 86 private final ShadeController mShadeController; 87 private final KeyguardMonitor mKeyguardMonitor; 88 private final ActivityStarter mActivityStarter; 89 private final NotificationEntryManager mEntryManager; 90 private final StatusBarStateController mStatusBarStateController; 91 private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider; 92 private final MetricsLogger mMetricsLogger; 93 private final Context mContext; 94 private final NotificationPanelView mNotificationPanel; 95 private final NotificationPresenter mPresenter; 96 private final LockPatternUtils mLockPatternUtils; 97 private final HeadsUpManagerPhone mHeadsUpManager; 98 private final KeyguardManager mKeyguardManager; 99 private final ActivityLaunchAnimator mActivityLaunchAnimator; 100 private final IStatusBarService mBarService; 101 private final CommandQueue mCommandQueue; 102 private final IDreamManager mDreamManager; 103 private final Handler mMainThreadHandler; 104 private final Handler mBackgroundHandler; 105 private final ActivityIntentHelper mActivityIntentHelper; 106 private final BubbleController mBubbleController; 107 108 private boolean mIsCollapsingToShowActivityOverLockscreen; 109 StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue, AssistManager assistManager, NotificationPanelView panel, NotificationPresenter presenter, NotificationEntryManager entryManager, HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter, ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService, StatusBarStateController statusBarStateController, KeyguardManager keyguardManager, IDreamManager dreamManager, NotificationRemoteInputManager remoteInputManager, StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager, NotificationLockscreenUserManager lockscreenUserManager, ShadeController shadeController, KeyguardMonitor keyguardMonitor, NotificationInterruptionStateProvider notificationInterruptionStateProvider, MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, Handler mainThreadHandler, Handler backgroundHandler, ActivityIntentHelper activityIntentHelper, BubbleController bubbleController)110 public StatusBarNotificationActivityStarter(Context context, 111 CommandQueue commandQueue, 112 AssistManager assistManager, 113 NotificationPanelView panel, 114 NotificationPresenter presenter, 115 NotificationEntryManager entryManager, 116 HeadsUpManagerPhone headsUpManager, 117 ActivityStarter activityStarter, 118 ActivityLaunchAnimator activityLaunchAnimator, 119 IStatusBarService statusBarService, 120 StatusBarStateController statusBarStateController, 121 KeyguardManager keyguardManager, 122 IDreamManager dreamManager, 123 NotificationRemoteInputManager remoteInputManager, 124 StatusBarRemoteInputCallback remoteInputCallback, 125 NotificationGroupManager groupManager, 126 NotificationLockscreenUserManager lockscreenUserManager, 127 ShadeController shadeController, 128 KeyguardMonitor keyguardMonitor, 129 NotificationInterruptionStateProvider notificationInterruptionStateProvider, 130 MetricsLogger metricsLogger, 131 LockPatternUtils lockPatternUtils, 132 Handler mainThreadHandler, 133 Handler backgroundHandler, 134 ActivityIntentHelper activityIntentHelper, 135 BubbleController bubbleController) { 136 mContext = context; 137 mNotificationPanel = panel; 138 mPresenter = presenter; 139 mHeadsUpManager = headsUpManager; 140 mActivityLaunchAnimator = activityLaunchAnimator; 141 mBarService = statusBarService; 142 mCommandQueue = commandQueue; 143 mKeyguardManager = keyguardManager; 144 mDreamManager = dreamManager; 145 mRemoteInputManager = remoteInputManager; 146 mLockscreenUserManager = lockscreenUserManager; 147 mShadeController = shadeController; 148 mKeyguardMonitor = keyguardMonitor; 149 mActivityStarter = activityStarter; 150 mEntryManager = entryManager; 151 mStatusBarStateController = statusBarStateController; 152 mNotificationInterruptionStateProvider = notificationInterruptionStateProvider; 153 mMetricsLogger = metricsLogger; 154 mAssistManager = assistManager; 155 mGroupManager = groupManager; 156 mLockPatternUtils = lockPatternUtils; 157 mBackgroundHandler = backgroundHandler; 158 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { 159 @Override 160 public void onPendingEntryAdded(NotificationEntry entry) { 161 handleFullScreenIntent(entry); 162 } 163 }); 164 mStatusBarRemoteInputCallback = remoteInputCallback; 165 mMainThreadHandler = mainThreadHandler; 166 mActivityIntentHelper = activityIntentHelper; 167 mBubbleController = bubbleController; 168 } 169 170 /** 171 * Called when a notification is clicked. 172 * 173 * @param sbn notification that was clicked 174 * @param row row for that notification 175 */ 176 @Override onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row)177 public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { 178 RemoteInputController controller = mRemoteInputManager.getController(); 179 if (controller.isRemoteInputActive(row.getEntry()) 180 && !TextUtils.isEmpty(row.getActiveRemoteInputText())) { 181 // We have an active remote input typed and the user clicked on the notification. 182 // this was probably unintentional, so we're closing the edit text instead. 183 controller.closeRemoteInputs(); 184 return; 185 } 186 Notification notification = sbn.getNotification(); 187 final PendingIntent intent = notification.contentIntent != null 188 ? notification.contentIntent 189 : notification.fullScreenIntent; 190 final boolean isBubble = row.getEntry().isBubble(); 191 192 // This code path is now executed for notification without a contentIntent. 193 // The only valid case is Bubble notifications. Guard against other cases 194 // entering here. 195 if (intent == null && !isBubble) { 196 Log.e(TAG, "onNotificationClicked called for non-clickable notification!"); 197 return; 198 } 199 200 final String notificationKey = sbn.getKey(); 201 202 boolean isActivityIntent = intent != null && intent.isActivity() && !isBubble; 203 final boolean afterKeyguardGone = isActivityIntent 204 && mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(), 205 mLockscreenUserManager.getCurrentUserId()); 206 final boolean wasOccluded = mShadeController.isOccluded(); 207 boolean showOverLockscreen = mKeyguardMonitor.isShowing() && intent != null 208 && mActivityIntentHelper.wouldShowOverLockscreen(intent.getIntent(), 209 mLockscreenUserManager.getCurrentUserId()); 210 ActivityStarter.OnDismissAction postKeyguardAction = 211 () -> handleNotificationClickAfterKeyguardDismissed( 212 sbn, row, controller, intent, notificationKey, 213 isActivityIntent, wasOccluded, showOverLockscreen); 214 if (showOverLockscreen) { 215 mIsCollapsingToShowActivityOverLockscreen = true; 216 postKeyguardAction.onDismiss(); 217 } else { 218 mActivityStarter.dismissKeyguardThenExecute( 219 postKeyguardAction, null /* cancel */, afterKeyguardGone); 220 } 221 } 222 handleNotificationClickAfterKeyguardDismissed( StatusBarNotification sbn, ExpandableNotificationRow row, RemoteInputController controller, PendingIntent intent, String notificationKey, boolean isActivityIntent, boolean wasOccluded, boolean showOverLockscreen)223 private boolean handleNotificationClickAfterKeyguardDismissed( 224 StatusBarNotification sbn, 225 ExpandableNotificationRow row, 226 RemoteInputController controller, 227 PendingIntent intent, 228 String notificationKey, 229 boolean isActivityIntent, 230 boolean wasOccluded, 231 boolean showOverLockscreen) { 232 // TODO: Some of this code may be able to move to NotificationEntryManager. 233 if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(notificationKey)) { 234 // Release the HUN notification to the shade. 235 236 if (mPresenter.isPresenterFullyCollapsed()) { 237 HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); 238 } 239 // 240 // In most cases, when FLAG_AUTO_CANCEL is set, the notification will 241 // become canceled shortly by NoMan, but we can't assume that. 242 mHeadsUpManager.removeNotification(sbn.getKey(), 243 true /* releaseImmediately */); 244 } 245 StatusBarNotification parentToCancel = null; 246 if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) { 247 StatusBarNotification summarySbn = 248 mGroupManager.getLogicalGroupSummary(sbn).notification; 249 if (shouldAutoCancel(summarySbn)) { 250 parentToCancel = summarySbn; 251 } 252 } 253 final StatusBarNotification parentToCancelFinal = parentToCancel; 254 final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( 255 sbn, row, controller, intent, notificationKey, 256 isActivityIntent, wasOccluded, parentToCancelFinal); 257 258 if (showOverLockscreen) { 259 mShadeController.addPostCollapseAction(runnable); 260 mShadeController.collapsePanel(true /* animate */); 261 } else if (mKeyguardMonitor.isShowing() 262 && mShadeController.isOccluded()) { 263 mShadeController.addAfterKeyguardGoneRunnable(runnable); 264 mShadeController.collapsePanel(); 265 } else { 266 mBackgroundHandler.postAtFrontOfQueue(runnable); 267 } 268 return !mNotificationPanel.isFullyCollapsed(); 269 } 270 handleNotificationClickAfterPanelCollapsed( StatusBarNotification sbn, ExpandableNotificationRow row, RemoteInputController controller, PendingIntent intent, String notificationKey, boolean isActivityIntent, boolean wasOccluded, StatusBarNotification parentToCancelFinal)271 private void handleNotificationClickAfterPanelCollapsed( 272 StatusBarNotification sbn, 273 ExpandableNotificationRow row, 274 RemoteInputController controller, 275 PendingIntent intent, 276 String notificationKey, 277 boolean isActivityIntent, 278 boolean wasOccluded, 279 StatusBarNotification parentToCancelFinal) { 280 try { 281 // The intent we are sending is for the application, which 282 // won't have permission to immediately start an activity after 283 // the user switches to home. We know it is safe to do at this 284 // point, so make sure new activity switches are now allowed. 285 ActivityManager.getService().resumeAppSwitches(); 286 } catch (RemoteException e) { 287 } 288 // If we are launching a work activity and require to launch 289 // separate work challenge, we defer the activity action and cancel 290 // notification until work challenge is unlocked. 291 if (isActivityIntent) { 292 final int userId = intent.getCreatorUserHandle().getIdentifier(); 293 if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) 294 && mKeyguardManager.isDeviceLocked(userId)) { 295 // TODO(b/28935539): should allow certain activities to 296 // bypass work challenge 297 if (mStatusBarRemoteInputCallback.startWorkChallengeIfNecessary(userId, 298 intent.getIntentSender(), notificationKey)) { 299 // Show work challenge, do not run PendingIntent and 300 // remove notification 301 collapseOnMainThread(); 302 return; 303 } 304 } 305 } 306 Intent fillInIntent = null; 307 NotificationEntry entry = row.getEntry(); 308 final boolean isBubble = entry.isBubble(); 309 CharSequence remoteInputText = null; 310 if (!TextUtils.isEmpty(entry.remoteInputText)) { 311 remoteInputText = entry.remoteInputText; 312 } 313 if (!TextUtils.isEmpty(remoteInputText) && !controller.isSpinning(entry.key)) { 314 fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT, 315 remoteInputText.toString()); 316 } 317 if (isBubble) { 318 expandBubbleStackOnMainThread(notificationKey); 319 } else { 320 startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent); 321 } 322 if (isActivityIntent || isBubble) { 323 mAssistManager.hideAssist(); 324 } 325 if (shouldCollapse()) { 326 collapseOnMainThread(); 327 } 328 329 final int count = 330 mEntryManager.getNotificationData().getActiveNotifications().size(); 331 final int rank = mEntryManager.getNotificationData().getRank(notificationKey); 332 NotificationVisibility.NotificationLocation location = 333 NotificationLogger.getNotificationLocation( 334 mEntryManager.getNotificationData().get(notificationKey)); 335 final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey, 336 rank, count, true, location); 337 try { 338 mBarService.onNotificationClick(notificationKey, nv); 339 } catch (RemoteException ex) { 340 // system process is dead if we're here. 341 } 342 if (!isBubble) { 343 if (parentToCancelFinal != null) { 344 removeNotification(parentToCancelFinal); 345 } 346 if (shouldAutoCancel(sbn) 347 || mRemoteInputManager.isNotificationKeptForRemoteInputHistory( 348 notificationKey)) { 349 // Automatically remove all notifications that we may have kept around longer 350 removeNotification(sbn); 351 } 352 } 353 mIsCollapsingToShowActivityOverLockscreen = false; 354 } 355 expandBubbleStackOnMainThread(String notificationKey)356 private void expandBubbleStackOnMainThread(String notificationKey) { 357 if (Looper.getMainLooper().isCurrentThread()) { 358 mBubbleController.expandStackAndSelectBubble(notificationKey); 359 } else { 360 mMainThreadHandler.post( 361 () -> mBubbleController.expandStackAndSelectBubble(notificationKey)); 362 } 363 } 364 startNotificationIntent(PendingIntent intent, Intent fillInIntent, ExpandableNotificationRow row, boolean wasOccluded, boolean isActivityIntent)365 private void startNotificationIntent(PendingIntent intent, Intent fillInIntent, 366 ExpandableNotificationRow row, boolean wasOccluded, boolean isActivityIntent) { 367 RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row, 368 wasOccluded); 369 try { 370 if (adapter != null) { 371 ActivityTaskManager.getService() 372 .registerRemoteAnimationForNextActivityStart( 373 intent.getCreatorPackage(), adapter); 374 } 375 int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, 376 null, null, getActivityOptions(adapter)); 377 mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent); 378 } catch (RemoteException | PendingIntent.CanceledException e) { 379 // the stack trace isn't very helpful here. 380 // Just log the exception message. 381 Log.w(TAG, "Sending contentIntent failed: " + e); 382 // TODO: Dismiss Keyguard. 383 } 384 } 385 386 @Override startNotificationGutsIntent(final Intent intent, final int appUid, ExpandableNotificationRow row)387 public void startNotificationGutsIntent(final Intent intent, final int appUid, 388 ExpandableNotificationRow row) { 389 mActivityStarter.dismissKeyguardThenExecute(() -> { 390 AsyncTask.execute(() -> { 391 int launchResult = TaskStackBuilder.create(mContext) 392 .addNextIntentWithParentStack(intent) 393 .startActivities(getActivityOptions( 394 mActivityLaunchAnimator.getLaunchAnimation( 395 row, mShadeController.isOccluded())), 396 new UserHandle(UserHandle.getUserId(appUid))); 397 mActivityLaunchAnimator.setLaunchResult(launchResult, true /* isActivityIntent */); 398 if (shouldCollapse()) { 399 // Putting it back on the main thread, since we're touching views 400 mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels( 401 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */)); 402 } 403 }); 404 return true; 405 }, null, false /* afterKeyguardGone */); 406 } 407 handleFullScreenIntent(NotificationEntry entry)408 private void handleFullScreenIntent(NotificationEntry entry) { 409 if (mNotificationInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { 410 if (shouldSuppressFullScreenIntent(entry)) { 411 if (DEBUG) { 412 Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.key); 413 } 414 } else if (entry.importance < NotificationManager.IMPORTANCE_HIGH) { 415 if (DEBUG) { 416 Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.key); 417 } 418 } else { 419 // Stop screensaver if the notification has a fullscreen intent. 420 // (like an incoming phone call) 421 Dependency.get(UiOffloadThread.class).submit(() -> { 422 try { 423 mDreamManager.awaken(); 424 } catch (RemoteException e) { 425 e.printStackTrace(); 426 } 427 }); 428 429 // not immersive & a fullscreen alert should be shown 430 if (DEBUG) { 431 Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); 432 } 433 try { 434 EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, 435 entry.key); 436 entry.notification.getNotification().fullScreenIntent.send(); 437 entry.notifyFullScreenIntentLaunched(); 438 mMetricsLogger.count("note_fullscreen", 1); 439 } catch (PendingIntent.CanceledException e) { 440 // ignore 441 } 442 } 443 } 444 } 445 446 @Override isCollapsingToShowActivityOverLockscreen()447 public boolean isCollapsingToShowActivityOverLockscreen() { 448 return mIsCollapsingToShowActivityOverLockscreen; 449 } 450 shouldAutoCancel(StatusBarNotification sbn)451 private static boolean shouldAutoCancel(StatusBarNotification sbn) { 452 int flags = sbn.getNotification().flags; 453 if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { 454 return false; 455 } 456 if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { 457 return false; 458 } 459 return true; 460 } 461 collapseOnMainThread()462 private void collapseOnMainThread() { 463 if (Looper.getMainLooper().isCurrentThread()) { 464 mShadeController.collapsePanel(); 465 } else { 466 mMainThreadHandler.post(mShadeController::collapsePanel); 467 } 468 } 469 shouldCollapse()470 private boolean shouldCollapse() { 471 return mStatusBarStateController.getState() != StatusBarState.SHADE 472 || !mActivityLaunchAnimator.isAnimationPending(); 473 } 474 shouldSuppressFullScreenIntent(NotificationEntry entry)475 private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) { 476 if (mPresenter.isDeviceInVrMode()) { 477 return true; 478 } 479 480 return entry.shouldSuppressFullScreenIntent(); 481 } 482 removeNotification(StatusBarNotification notification)483 private void removeNotification(StatusBarNotification notification) { 484 // We have to post it to the UI thread for synchronization 485 mMainThreadHandler.post(() -> { 486 Runnable removeRunnable = 487 () -> mEntryManager.performRemoveNotification(notification, REASON_CLICK); 488 if (mPresenter.isCollapsing()) { 489 // To avoid lags we're only performing the remove 490 // after the shade was collapsed 491 mShadeController.addPostCollapseAction(removeRunnable); 492 } else { 493 removeRunnable.run(); 494 } 495 }); 496 } 497 } 498