1 /* 2 * Copyright (C) 2019 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.shade; 18 19 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; 20 21 import android.app.StatusBarManager; 22 import android.media.AudioManager; 23 import android.media.session.MediaSessionLegacyHelper; 24 import android.os.PowerManager; 25 import android.os.SystemClock; 26 import android.util.Log; 27 import android.view.GestureDetector; 28 import android.view.InputDevice; 29 import android.view.KeyEvent; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.ViewGroup; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.keyguard.AuthKeyguardMessageArea; 36 import com.android.keyguard.LockIconViewController; 37 import com.android.keyguard.dagger.KeyguardBouncerComponent; 38 import com.android.systemui.R; 39 import com.android.systemui.classifier.FalsingCollector; 40 import com.android.systemui.dock.DockManager; 41 import com.android.systemui.keyguard.KeyguardUnlockAnimationController; 42 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; 43 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; 44 import com.android.systemui.keyguard.shared.model.TransitionState; 45 import com.android.systemui.keyguard.shared.model.TransitionStep; 46 import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; 47 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; 48 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; 49 import com.android.systemui.statusbar.DragDownHelper; 50 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 51 import com.android.systemui.statusbar.NotificationInsetsController; 52 import com.android.systemui.statusbar.NotificationShadeDepthController; 53 import com.android.systemui.statusbar.NotificationShadeWindowController; 54 import com.android.systemui.statusbar.SysuiStatusBarStateController; 55 import com.android.systemui.statusbar.notification.stack.AmbientState; 56 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 57 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; 58 import com.android.systemui.statusbar.phone.CentralSurfaces; 59 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; 60 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 61 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; 62 import com.android.systemui.statusbar.window.StatusBarWindowStateController; 63 64 import java.io.PrintWriter; 65 import java.util.function.Consumer; 66 67 import javax.inject.Inject; 68 69 /** 70 * Controller for {@link NotificationShadeWindowView}. 71 */ 72 @CentralSurfacesComponent.CentralSurfacesScope 73 public class NotificationShadeWindowViewController { 74 private static final String TAG = "NotifShadeWindowVC"; 75 private final FalsingCollector mFalsingCollector; 76 private final SysuiStatusBarStateController mStatusBarStateController; 77 private final NotificationShadeWindowView mView; 78 private final NotificationShadeDepthController mDepthController; 79 private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; 80 private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; 81 private final LockIconViewController mLockIconViewController; 82 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 83 private final StatusBarWindowStateController mStatusBarWindowStateController; 84 private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; 85 private final AmbientState mAmbientState; 86 private final PulsingGestureListener mPulsingGestureListener; 87 private final NotificationInsetsController mNotificationInsetsController; 88 private final AlternateBouncerInteractor mAlternateBouncerInteractor; 89 90 private GestureDetector mPulsingWakeupGestureHandler; 91 private View mBrightnessMirror; 92 private boolean mTouchActive; 93 private boolean mTouchCancelled; 94 private boolean mExpandAnimationRunning; 95 private NotificationStackScrollLayout mStackScrollLayout; 96 private PhoneStatusBarViewController mStatusBarViewController; 97 private final CentralSurfaces mService; 98 private final NotificationShadeWindowController mNotificationShadeWindowController; 99 private DragDownHelper mDragDownHelper; 100 private boolean mExpandingBelowNotch; 101 private final DockManager mDockManager; 102 private final NotificationPanelViewController mNotificationPanelViewController; 103 private final ShadeExpansionStateManager mShadeExpansionStateManager; 104 105 private boolean mIsTrackingBarGesture = false; 106 private boolean mIsOcclusionTransitionRunning = false; 107 108 private final Consumer<TransitionStep> mLockscreenToDreamingTransition = 109 (TransitionStep step) -> { 110 mIsOcclusionTransitionRunning = 111 step.getTransitionState() == TransitionState.RUNNING; 112 }; 113 114 @Inject NotificationShadeWindowViewController( LockscreenShadeTransitionController transitionController, FalsingCollector falsingCollector, SysuiStatusBarStateController statusBarStateController, DockManager dockManager, NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, NotificationPanelViewController notificationPanelViewController, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowStateController statusBarWindowStateController, LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, NotificationShadeWindowController controller, KeyguardUnlockAnimationController keyguardUnlockAnimationController, NotificationInsetsController notificationInsetsController, AmbientState ambientState, PulsingGestureListener pulsingGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, AlternateBouncerInteractor alternateBouncerInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel )115 public NotificationShadeWindowViewController( 116 LockscreenShadeTransitionController transitionController, 117 FalsingCollector falsingCollector, 118 SysuiStatusBarStateController statusBarStateController, 119 DockManager dockManager, 120 NotificationShadeDepthController depthController, 121 NotificationShadeWindowView notificationShadeWindowView, 122 NotificationPanelViewController notificationPanelViewController, 123 ShadeExpansionStateManager shadeExpansionStateManager, 124 NotificationStackScrollLayoutController notificationStackScrollLayoutController, 125 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 126 StatusBarWindowStateController statusBarWindowStateController, 127 LockIconViewController lockIconViewController, 128 CentralSurfaces centralSurfaces, 129 NotificationShadeWindowController controller, 130 KeyguardUnlockAnimationController keyguardUnlockAnimationController, 131 NotificationInsetsController notificationInsetsController, 132 AmbientState ambientState, 133 PulsingGestureListener pulsingGestureListener, 134 KeyguardBouncerViewModel keyguardBouncerViewModel, 135 KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, 136 AlternateBouncerInteractor alternateBouncerInteractor, 137 KeyguardTransitionInteractor keyguardTransitionInteractor, 138 PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel 139 ) { 140 mLockscreenShadeTransitionController = transitionController; 141 mFalsingCollector = falsingCollector; 142 mStatusBarStateController = statusBarStateController; 143 mView = notificationShadeWindowView; 144 mDockManager = dockManager; 145 mNotificationPanelViewController = notificationPanelViewController; 146 mShadeExpansionStateManager = shadeExpansionStateManager; 147 mDepthController = depthController; 148 mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; 149 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 150 mStatusBarWindowStateController = statusBarWindowStateController; 151 mLockIconViewController = lockIconViewController; 152 mService = centralSurfaces; 153 mNotificationShadeWindowController = controller; 154 mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; 155 mAmbientState = ambientState; 156 mPulsingGestureListener = pulsingGestureListener; 157 mNotificationInsetsController = notificationInsetsController; 158 mAlternateBouncerInteractor = alternateBouncerInteractor; 159 160 // This view is not part of the newly inflated expanded status bar. 161 mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); 162 KeyguardBouncerViewBinder.bind( 163 mView.findViewById(R.id.keyguard_bouncer_container), 164 keyguardBouncerViewModel, 165 primaryBouncerToGoneTransitionViewModel, 166 keyguardBouncerComponentFactory); 167 168 collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(), 169 mLockscreenToDreamingTransition); 170 } 171 172 /** 173 * @return Location where to place the KeyguardBouncer 174 */ getBouncerContainer()175 public ViewGroup getBouncerContainer() { 176 return mView.findViewById(R.id.keyguard_bouncer_container); 177 } 178 179 /** 180 * @return Location where to place the KeyguardMessageArea 181 */ getKeyguardMessageArea()182 public AuthKeyguardMessageArea getKeyguardMessageArea() { 183 return mView.findViewById(R.id.keyguard_message_area); 184 } 185 186 /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ setupExpandedStatusBar()187 public void setupExpandedStatusBar() { 188 mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); 189 mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(), 190 mPulsingGestureListener); 191 192 mView.setLayoutInsetsController(mNotificationInsetsController); 193 mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { 194 @Override 195 public Boolean handleDispatchTouchEvent(MotionEvent ev) { 196 if (mStatusBarViewController == null) { // Fix for b/192490822 197 Log.w(TAG, "Ignoring touch while statusBarView not yet set."); 198 return false; 199 } 200 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; 201 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 202 boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; 203 204 boolean expandingBelowNotch = mExpandingBelowNotch; 205 if (isUp || isCancel) { 206 mExpandingBelowNotch = false; 207 } 208 209 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still 210 // gets 211 // delivered. 212 if (!isCancel && mService.shouldIgnoreTouch()) { 213 return false; 214 } 215 216 if (isDown) { 217 mTouchActive = true; 218 mTouchCancelled = false; 219 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP 220 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { 221 mTouchActive = false; 222 } 223 if (mTouchCancelled || mExpandAnimationRunning) { 224 return false; 225 } 226 227 if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { 228 // If the user was sliding their finger across the lock screen, 229 // we may have been intercepting the touch and forwarding it to the 230 // UDFPS affordance via mStatusBarKeyguardViewManager.onTouch (see below). 231 // If this touch ended up unlocking the device, we want to cancel the touch 232 // immediately, so we don't cause swipe or expand animations afterwards. 233 cancelCurrentTouch(); 234 return true; 235 } 236 237 if (mIsOcclusionTransitionRunning) { 238 return false; 239 } 240 241 mFalsingCollector.onTouchEvent(ev); 242 mPulsingWakeupGestureHandler.onTouchEvent(ev); 243 if (mStatusBarKeyguardViewManager.onTouch(ev)) { 244 return true; 245 } 246 if (mBrightnessMirror != null 247 && mBrightnessMirror.getVisibility() == View.VISIBLE) { 248 // Disallow new pointers while the brightness mirror is visible. This is so that 249 // you can't touch anything other than the brightness slider while the mirror is 250 // showing and the rest of the panel is transparent. 251 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 252 return false; 253 } 254 } 255 if (isDown) { 256 mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); 257 } 258 259 if (mStatusBarStateController.isDozing()) { 260 mService.extendDozePulse(); 261 } 262 mLockIconViewController.onTouchEvent( 263 ev, 264 () -> mService.wakeUpIfDozing( 265 SystemClock.uptimeMillis(), 266 mView, 267 "LOCK_ICON_TOUCH", 268 PowerManager.WAKE_REASON_GESTURE) 269 ); 270 271 // In case we start outside of the view bounds (below the status bar), we need to 272 // dispatch 273 // the touch manually as the view system can't accommodate for touches outside of 274 // the 275 // regular view bounds. 276 if (isDown && ev.getY() >= mView.getBottom()) { 277 mExpandingBelowNotch = true; 278 expandingBelowNotch = true; 279 } 280 if (expandingBelowNotch) { 281 return mStatusBarViewController.sendTouchToView(ev); 282 } 283 284 if (!mIsTrackingBarGesture && isDown 285 && mNotificationPanelViewController.isFullyCollapsed()) { 286 float x = ev.getRawX(); 287 float y = ev.getRawY(); 288 if (mStatusBarViewController.touchIsWithinView(x, y)) { 289 if (mStatusBarWindowStateController.windowIsShowing()) { 290 mIsTrackingBarGesture = true; 291 return mStatusBarViewController.sendTouchToView(ev); 292 } else { // it's hidden or hiding, don't send to notification shade. 293 return true; 294 } 295 } 296 } else if (mIsTrackingBarGesture) { 297 final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev); 298 if (isUp || isCancel) { 299 mIsTrackingBarGesture = false; 300 } 301 return sendToStatusBar; 302 } 303 304 return null; 305 } 306 307 @Override 308 public void dispatchTouchEventComplete() { 309 mFalsingCollector.onMotionEventComplete(); 310 } 311 312 @Override 313 public boolean shouldInterceptTouchEvent(MotionEvent ev) { 314 if (mStatusBarStateController.isDozing() && !mService.isPulsing() 315 && !mDockManager.isDocked()) { 316 // Capture all touch events in always-on. 317 return true; 318 } 319 320 if (mAlternateBouncerInteractor.isVisibleState()) { 321 // capture all touches if the alt auth bouncer is showing 322 return true; 323 } 324 325 if (mLockIconViewController.onInterceptTouchEvent(ev)) { 326 // immediately return true; don't send the touch to the drag down helper 327 return true; 328 } 329 330 boolean intercept = false; 331 if (mNotificationPanelViewController.isFullyExpanded() 332 && mDragDownHelper.isDragDownEnabled() 333 && !mService.isBouncerShowing() 334 && !mStatusBarStateController.isDozing()) { 335 intercept = mDragDownHelper.onInterceptTouchEvent(ev); 336 } 337 338 return intercept; 339 340 } 341 342 @Override 343 public void didIntercept(MotionEvent ev) { 344 MotionEvent cancellation = MotionEvent.obtain(ev); 345 cancellation.setAction(MotionEvent.ACTION_CANCEL); 346 mStackScrollLayout.onInterceptTouchEvent(cancellation); 347 mNotificationPanelViewController.handleExternalInterceptTouch(cancellation); 348 cancellation.recycle(); 349 } 350 351 @Override 352 public boolean handleTouchEvent(MotionEvent ev) { 353 boolean handled = false; 354 if (mStatusBarStateController.isDozing()) { 355 handled = !mService.isPulsing(); 356 } 357 358 if (mAlternateBouncerInteractor.isVisibleState()) { 359 // eat the touch 360 handled = true; 361 } 362 363 if ((mDragDownHelper.isDragDownEnabled() && !handled) 364 || mDragDownHelper.isDraggingDown()) { 365 // we still want to finish our drag down gesture when locking the screen 366 handled = mDragDownHelper.onTouchEvent(ev); 367 } 368 369 return handled; 370 } 371 372 @Override 373 public void didNotHandleTouchEvent(MotionEvent ev) { 374 final int action = ev.getActionMasked(); 375 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 376 mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); 377 } 378 } 379 380 @Override 381 public boolean interceptMediaKey(KeyEvent event) { 382 return mService.interceptMediaKey(event); 383 } 384 385 @Override 386 public boolean dispatchKeyEventPreIme(KeyEvent event) { 387 return mService.dispatchKeyEventPreIme(event); 388 } 389 390 @Override 391 public boolean dispatchKeyEvent(KeyEvent event) { 392 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 393 switch (event.getKeyCode()) { 394 case KeyEvent.KEYCODE_BACK: 395 if (!down) { 396 mService.onBackPressed(); 397 } 398 return true; 399 case KeyEvent.KEYCODE_MENU: 400 if (!down) { 401 return mService.onMenuPressed(); 402 } 403 break; 404 case KeyEvent.KEYCODE_SPACE: 405 if (!down) { 406 return mService.onSpacePressed(); 407 } 408 break; 409 case KeyEvent.KEYCODE_VOLUME_DOWN: 410 case KeyEvent.KEYCODE_VOLUME_UP: 411 if (mStatusBarStateController.isDozing()) { 412 MediaSessionLegacyHelper.getHelper(mView.getContext()) 413 .sendVolumeKeyEvent( 414 event, AudioManager.USE_DEFAULT_STREAM_TYPE, true); 415 return true; 416 } 417 break; 418 } 419 return false; 420 } 421 }); 422 423 mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 424 @Override 425 public void onChildViewAdded(View parent, View child) { 426 if (child.getId() == R.id.brightness_mirror_container) { 427 mBrightnessMirror = child; 428 } 429 } 430 431 @Override 432 public void onChildViewRemoved(View parent, View child) { 433 } 434 }); 435 436 setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); 437 438 mDepthController.setRoot(mView); 439 mShadeExpansionStateManager.addExpansionListener(mDepthController); 440 } 441 getView()442 public NotificationShadeWindowView getView() { 443 return mView; 444 } 445 cancelCurrentTouch()446 public void cancelCurrentTouch() { 447 if (mTouchActive) { 448 final long now = SystemClock.uptimeMillis(); 449 MotionEvent event = MotionEvent.obtain(now, now, 450 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 451 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 452 mView.dispatchTouchEvent(event); 453 event.recycle(); 454 mTouchCancelled = true; 455 } 456 mAmbientState.setSwipingUp(false); 457 } 458 dump(PrintWriter pw, String[] args)459 public void dump(PrintWriter pw, String[] args) { 460 pw.print(" mExpandAnimationRunning="); 461 pw.println(mExpandAnimationRunning); 462 pw.print(" mTouchCancelled="); 463 pw.println(mTouchCancelled); 464 pw.print(" mTouchActive="); 465 pw.println(mTouchActive); 466 } 467 setExpandAnimationRunning(boolean running)468 public void setExpandAnimationRunning(boolean running) { 469 if (mExpandAnimationRunning != running) { 470 mExpandAnimationRunning = running; 471 mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning); 472 } 473 } 474 cancelExpandHelper()475 public void cancelExpandHelper() { 476 if (mStackScrollLayout != null) { 477 mStackScrollLayout.cancelExpandHelper(); 478 } 479 } 480 setStatusBarViewController(PhoneStatusBarViewController statusBarViewController)481 public void setStatusBarViewController(PhoneStatusBarViewController statusBarViewController) { 482 mStatusBarViewController = statusBarViewController; 483 } 484 485 @VisibleForTesting setDragDownHelper(DragDownHelper dragDownHelper)486 void setDragDownHelper(DragDownHelper dragDownHelper) { 487 mDragDownHelper = dragDownHelper; 488 } 489 } 490