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.statusbar.phone; 18 19 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; 20 21 import android.app.StatusBarManager; 22 import android.graphics.RectF; 23 import android.hardware.display.AmbientDisplayConfiguration; 24 import android.media.AudioManager; 25 import android.media.session.MediaSessionLegacyHelper; 26 import android.os.SystemClock; 27 import android.os.UserHandle; 28 import android.provider.Settings; 29 import android.util.Log; 30 import android.view.GestureDetector; 31 import android.view.InputDevice; 32 import android.view.KeyEvent; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.ViewGroup; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.keyguard.LockIconViewController; 39 import com.android.systemui.R; 40 import com.android.systemui.classifier.FalsingCollector; 41 import com.android.systemui.dock.DockManager; 42 import com.android.systemui.doze.DozeLog; 43 import com.android.systemui.shared.plugins.PluginManager; 44 import com.android.systemui.statusbar.CommandQueue; 45 import com.android.systemui.statusbar.DragDownHelper; 46 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 47 import com.android.systemui.statusbar.NotificationLockscreenUserManager; 48 import com.android.systemui.statusbar.NotificationShadeDepthController; 49 import com.android.systemui.statusbar.NotificationShadeWindowController; 50 import com.android.systemui.statusbar.PulseExpansionHandler; 51 import com.android.systemui.statusbar.SysuiStatusBarStateController; 52 import com.android.systemui.statusbar.notification.DynamicPrivacyController; 53 import com.android.systemui.statusbar.notification.NotificationEntryManager; 54 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; 55 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 56 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; 57 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; 58 import com.android.systemui.statusbar.policy.KeyguardStateController; 59 import com.android.systemui.statusbar.window.StatusBarWindowController; 60 import com.android.systemui.tuner.TunerService; 61 62 import java.io.FileDescriptor; 63 import java.io.PrintWriter; 64 65 import javax.inject.Inject; 66 67 /** 68 * Controller for {@link NotificationShadeWindowView}. 69 */ 70 public class NotificationShadeWindowViewController { 71 private static final String TAG = "NotifShadeWindowVC"; 72 private final NotificationWakeUpCoordinator mCoordinator; 73 private final PulseExpansionHandler mPulseExpansionHandler; 74 private final DynamicPrivacyController mDynamicPrivacyController; 75 private final KeyguardBypassController mBypassController; 76 private final PluginManager mPluginManager; 77 private final FalsingCollector mFalsingCollector; 78 private final TunerService mTunerService; 79 private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; 80 private final NotificationEntryManager mNotificationEntryManager; 81 private final KeyguardStateController mKeyguardStateController; 82 private final SysuiStatusBarStateController mStatusBarStateController; 83 private final DozeLog mDozeLog; 84 private final DozeParameters mDozeParameters; 85 private final CommandQueue mCommandQueue; 86 private final NotificationShadeWindowView mView; 87 private final ShadeController mShadeController; 88 private final NotificationShadeDepthController mDepthController; 89 private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; 90 private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; 91 private final LockIconViewController mLockIconViewController; 92 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 93 94 private GestureDetector mGestureDetector; 95 private View mBrightnessMirror; 96 private boolean mTouchActive; 97 private boolean mTouchCancelled; 98 private boolean mExpandAnimationRunning; 99 private NotificationStackScrollLayout mStackScrollLayout; 100 private PhoneStatusBarView mStatusBarView; 101 private PhoneStatusBarTransitions mBarTransitions; 102 private StatusBar mService; 103 private NotificationShadeWindowController mNotificationShadeWindowController; 104 private DragDownHelper mDragDownHelper; 105 private boolean mDoubleTapEnabled; 106 private boolean mSingleTapEnabled; 107 private boolean mExpandingBelowNotch; 108 private final DockManager mDockManager; 109 private final NotificationPanelViewController mNotificationPanelViewController; 110 private final PanelExpansionStateManager mPanelExpansionStateManager; 111 private final StatusBarWindowController mStatusBarWindowController; 112 113 // Used for determining view / touch intersection 114 private int[] mTempLocation = new int[2]; 115 private RectF mTempRect = new RectF(); 116 private boolean mIsTrackingBarGesture = false; 117 118 @Inject NotificationShadeWindowViewController( NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, KeyguardBypassController bypassController, LockscreenShadeTransitionController transitionController, FalsingCollector falsingCollector, PluginManager pluginManager, TunerService tunerService, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, KeyguardStateController keyguardStateController, SysuiStatusBarStateController statusBarStateController, DozeLog dozeLog, DozeParameters dozeParameters, CommandQueue commandQueue, ShadeController shadeController, DockManager dockManager, NotificationShadeDepthController depthController, NotificationShadeWindowView notificationShadeWindowView, NotificationPanelViewController notificationPanelViewController, PanelExpansionStateManager panelExpansionStateManager, StatusBarWindowController statusBarWindowController, NotificationStackScrollLayoutController notificationStackScrollLayoutController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, LockIconViewController lockIconViewController)119 public NotificationShadeWindowViewController( 120 NotificationWakeUpCoordinator coordinator, 121 PulseExpansionHandler pulseExpansionHandler, 122 DynamicPrivacyController dynamicPrivacyController, 123 KeyguardBypassController bypassController, 124 LockscreenShadeTransitionController transitionController, 125 FalsingCollector falsingCollector, 126 PluginManager pluginManager, 127 TunerService tunerService, 128 NotificationLockscreenUserManager notificationLockscreenUserManager, 129 NotificationEntryManager notificationEntryManager, 130 KeyguardStateController keyguardStateController, 131 SysuiStatusBarStateController statusBarStateController, 132 DozeLog dozeLog, 133 DozeParameters dozeParameters, 134 CommandQueue commandQueue, 135 ShadeController shadeController, 136 DockManager dockManager, 137 NotificationShadeDepthController depthController, 138 NotificationShadeWindowView notificationShadeWindowView, 139 NotificationPanelViewController notificationPanelViewController, 140 PanelExpansionStateManager panelExpansionStateManager, 141 StatusBarWindowController statusBarWindowController, 142 NotificationStackScrollLayoutController notificationStackScrollLayoutController, 143 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 144 LockIconViewController lockIconViewController) { 145 mCoordinator = coordinator; 146 mPulseExpansionHandler = pulseExpansionHandler; 147 mDynamicPrivacyController = dynamicPrivacyController; 148 mBypassController = bypassController; 149 mLockscreenShadeTransitionController = transitionController; 150 mFalsingCollector = falsingCollector; 151 mPluginManager = pluginManager; 152 mTunerService = tunerService; 153 mNotificationLockscreenUserManager = notificationLockscreenUserManager; 154 mNotificationEntryManager = notificationEntryManager; 155 mKeyguardStateController = keyguardStateController; 156 mStatusBarStateController = statusBarStateController; 157 mDozeLog = dozeLog; 158 mDozeParameters = dozeParameters; 159 mCommandQueue = commandQueue; 160 mView = notificationShadeWindowView; 161 mShadeController = shadeController; 162 mDockManager = dockManager; 163 mNotificationPanelViewController = notificationPanelViewController; 164 mPanelExpansionStateManager = panelExpansionStateManager; 165 mDepthController = depthController; 166 mStatusBarWindowController = statusBarWindowController; 167 mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; 168 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 169 mLockIconViewController = lockIconViewController; 170 171 // This view is not part of the newly inflated expanded status bar. 172 mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); 173 } 174 175 /** 176 * @return Location where to place the KeyguardBouncer 177 */ getBouncerContainer()178 public ViewGroup getBouncerContainer() { 179 return mView.findViewById(R.id.keyguard_bouncer_container); 180 } 181 182 /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ setupExpandedStatusBar()183 public void setupExpandedStatusBar() { 184 mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller); 185 186 TunerService.Tunable tunable = (key, newValue) -> { 187 AmbientDisplayConfiguration configuration = 188 new AmbientDisplayConfiguration(mView.getContext()); 189 switch (key) { 190 case Settings.Secure.DOZE_DOUBLE_TAP_GESTURE: 191 mDoubleTapEnabled = configuration.doubleTapGestureEnabled( 192 UserHandle.USER_CURRENT); 193 break; 194 case Settings.Secure.DOZE_TAP_SCREEN_GESTURE: 195 mSingleTapEnabled = configuration.tapGestureEnabled(UserHandle.USER_CURRENT); 196 } 197 }; 198 mTunerService.addTunable(tunable, 199 Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, 200 Settings.Secure.DOZE_TAP_SCREEN_GESTURE); 201 202 GestureDetector.SimpleOnGestureListener gestureListener = 203 new GestureDetector.SimpleOnGestureListener() { 204 @Override 205 public boolean onSingleTapConfirmed(MotionEvent e) { 206 if (mSingleTapEnabled && !mDockManager.isDocked()) { 207 mService.wakeUpIfDozing( 208 SystemClock.uptimeMillis(), mView, "SINGLE_TAP"); 209 return true; 210 } 211 return false; 212 } 213 214 @Override 215 public boolean onDoubleTap(MotionEvent e) { 216 if (mDoubleTapEnabled || mSingleTapEnabled) { 217 mService.wakeUpIfDozing( 218 SystemClock.uptimeMillis(), mView, "DOUBLE_TAP"); 219 return true; 220 } 221 return false; 222 } 223 }; 224 mGestureDetector = new GestureDetector(mView.getContext(), gestureListener); 225 226 mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() { 227 @Override 228 public Boolean handleDispatchTouchEvent(MotionEvent ev) { 229 if (mStatusBarView == null) { 230 Log.w(TAG, "Ignoring touch while statusBarView not yet set."); 231 return false; 232 } 233 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN; 234 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; 235 boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL; 236 237 boolean expandingBelowNotch = mExpandingBelowNotch; 238 if (isUp || isCancel) { 239 mExpandingBelowNotch = false; 240 } 241 242 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still 243 // gets 244 // delivered. 245 246 if (!isCancel && mService.shouldIgnoreTouch()) { 247 return false; 248 } 249 250 if (isDown) { 251 setTouchActive(true); 252 mTouchCancelled = false; 253 } else if (ev.getActionMasked() == MotionEvent.ACTION_UP 254 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) { 255 setTouchActive(false); 256 } 257 if (mTouchCancelled || mExpandAnimationRunning) { 258 return false; 259 } 260 261 mFalsingCollector.onTouchEvent(ev); 262 mGestureDetector.onTouchEvent(ev); 263 mStatusBarKeyguardViewManager.onTouch(ev); 264 if (mBrightnessMirror != null 265 && mBrightnessMirror.getVisibility() == View.VISIBLE) { 266 // Disallow new pointers while the brightness mirror is visible. This is so that 267 // you can't touch anything other than the brightness slider while the mirror is 268 // showing and the rest of the panel is transparent. 269 if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { 270 return false; 271 } 272 } 273 if (isDown) { 274 mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); 275 } 276 277 if (mStatusBarStateController.isDozing()) { 278 mService.mDozeScrimController.extendPulse(); 279 } 280 mLockIconViewController.onTouchEvent( 281 ev, 282 () -> mService.wakeUpIfDozing( 283 SystemClock.uptimeMillis(), 284 mView, 285 "LOCK_ICON_TOUCH")); 286 287 // In case we start outside of the view bounds (below the status bar), we need to 288 // dispatch 289 // the touch manually as the view system can't accommodate for touches outside of 290 // the 291 // regular view bounds. 292 if (isDown && ev.getY() >= mView.getBottom()) { 293 mExpandingBelowNotch = true; 294 expandingBelowNotch = true; 295 } 296 if (expandingBelowNotch) { 297 return mStatusBarView.dispatchTouchEvent(ev); 298 } 299 300 if (!mIsTrackingBarGesture && isDown 301 && mNotificationPanelViewController.isFullyCollapsed()) { 302 float x = ev.getRawX(); 303 float y = ev.getRawY(); 304 if (isIntersecting(mStatusBarView, x, y)) { 305 if (mService.isSameStatusBarState(WINDOW_STATE_SHOWING)) { 306 mIsTrackingBarGesture = true; 307 return mStatusBarView.dispatchTouchEvent(ev); 308 } else { // it's hidden or hiding, don't send to notification shade. 309 return true; 310 } 311 } 312 } else if (mIsTrackingBarGesture) { 313 final boolean sendToNotification = mStatusBarView.dispatchTouchEvent(ev); 314 if (isUp || isCancel) { 315 mIsTrackingBarGesture = false; 316 } 317 return sendToNotification; 318 } 319 320 return null; 321 } 322 323 @Override 324 public void dispatchTouchEventComplete() { 325 mFalsingCollector.onMotionEventComplete(); 326 } 327 328 @Override 329 public boolean shouldInterceptTouchEvent(MotionEvent ev) { 330 if (mStatusBarStateController.isDozing() && !mService.isPulsing() 331 && !mDockManager.isDocked()) { 332 // Capture all touch events in always-on. 333 return true; 334 } 335 336 if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { 337 // capture all touches if the alt auth bouncer is showing 338 return true; 339 } 340 341 if (mLockIconViewController.onInterceptTouchEvent(ev)) { 342 // immediately return true; don't send the touch to the drag down helper 343 return true; 344 } 345 346 boolean intercept = false; 347 if (mNotificationPanelViewController.isFullyExpanded() 348 && mDragDownHelper.isDragDownEnabled() 349 && !mService.isBouncerShowing() 350 && !mStatusBarStateController.isDozing()) { 351 intercept = mDragDownHelper.onInterceptTouchEvent(ev); 352 } 353 354 return intercept; 355 356 } 357 358 @Override 359 public void didIntercept(MotionEvent ev) { 360 MotionEvent cancellation = MotionEvent.obtain(ev); 361 cancellation.setAction(MotionEvent.ACTION_CANCEL); 362 mStackScrollLayout.onInterceptTouchEvent(cancellation); 363 mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation); 364 cancellation.recycle(); 365 } 366 367 @Override 368 public boolean handleTouchEvent(MotionEvent ev) { 369 boolean handled = false; 370 if (mStatusBarStateController.isDozing()) { 371 handled = !mService.isPulsing(); 372 } 373 374 if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { 375 // eat the touch 376 handled = true; 377 } 378 379 if ((mDragDownHelper.isDragDownEnabled() && !handled) 380 || mDragDownHelper.isDraggingDown()) { 381 // we still want to finish our drag down gesture when locking the screen 382 handled = mDragDownHelper.onTouchEvent(ev); 383 } 384 385 return handled; 386 } 387 388 @Override 389 public void didNotHandleTouchEvent(MotionEvent ev) { 390 final int action = ev.getActionMasked(); 391 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 392 mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); 393 } 394 } 395 396 @Override 397 public boolean interceptMediaKey(KeyEvent event) { 398 return mService.interceptMediaKey(event); 399 } 400 401 @Override 402 public boolean dispatchKeyEventPreIme(KeyEvent event) { 403 return mService.dispatchKeyEventPreIme(event); 404 } 405 406 @Override 407 public boolean dispatchKeyEvent(KeyEvent event) { 408 boolean down = event.getAction() == KeyEvent.ACTION_DOWN; 409 switch (event.getKeyCode()) { 410 case KeyEvent.KEYCODE_BACK: 411 if (!down) { 412 mService.onBackPressed(); 413 } 414 return true; 415 case KeyEvent.KEYCODE_MENU: 416 if (!down) { 417 return mService.onMenuPressed(); 418 } 419 break; 420 case KeyEvent.KEYCODE_SPACE: 421 if (!down) { 422 return mService.onSpacePressed(); 423 } 424 break; 425 case KeyEvent.KEYCODE_VOLUME_DOWN: 426 case KeyEvent.KEYCODE_VOLUME_UP: 427 if (mStatusBarStateController.isDozing()) { 428 MediaSessionLegacyHelper.getHelper(mView.getContext()) 429 .sendVolumeKeyEvent( 430 event, AudioManager.USE_DEFAULT_STREAM_TYPE, true); 431 return true; 432 } 433 break; 434 } 435 return false; 436 } 437 }); 438 439 mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { 440 @Override 441 public void onChildViewAdded(View parent, View child) { 442 if (child.getId() == R.id.brightness_mirror_container) { 443 mBrightnessMirror = child; 444 } 445 } 446 447 @Override 448 public void onChildViewRemoved(View parent, View child) { 449 } 450 }); 451 452 setDragDownHelper(mLockscreenShadeTransitionController.getTouchHelper()); 453 454 mDepthController.setRoot(mView); 455 mPanelExpansionStateManager.addExpansionListener(mDepthController); 456 } 457 getView()458 public NotificationShadeWindowView getView() { 459 return mView; 460 } 461 setTouchActive(boolean touchActive)462 public void setTouchActive(boolean touchActive) { 463 mTouchActive = touchActive; 464 } 465 cancelCurrentTouch()466 public void cancelCurrentTouch() { 467 if (mTouchActive) { 468 final long now = SystemClock.uptimeMillis(); 469 MotionEvent event = MotionEvent.obtain(now, now, 470 MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); 471 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 472 mView.dispatchTouchEvent(event); 473 event.recycle(); 474 mTouchCancelled = true; 475 } 476 } 477 dump(FileDescriptor fd, PrintWriter pw, String[] args)478 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 479 pw.print(" mExpandAnimationRunning="); 480 pw.println(mExpandAnimationRunning); 481 pw.print(" mTouchCancelled="); 482 pw.println(mTouchCancelled); 483 pw.print(" mTouchActive="); 484 pw.println(mTouchActive); 485 } 486 setExpandAnimationRunning(boolean running)487 public void setExpandAnimationRunning(boolean running) { 488 if (mExpandAnimationRunning != running) { 489 mExpandAnimationRunning = running; 490 mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning); 491 } 492 } 493 cancelExpandHelper()494 public void cancelExpandHelper() { 495 if (mStackScrollLayout != null) { 496 mStackScrollLayout.cancelExpandHelper(); 497 } 498 } 499 getBarTransitions()500 public PhoneStatusBarTransitions getBarTransitions() { 501 return mBarTransitions; 502 } 503 setStatusBarView(PhoneStatusBarView statusBarView)504 public void setStatusBarView(PhoneStatusBarView statusBarView) { 505 mStatusBarView = statusBarView; 506 if (statusBarView != null) { 507 mBarTransitions = new PhoneStatusBarTransitions( 508 statusBarView, 509 mStatusBarWindowController.getBackgroundView()); 510 } 511 } 512 setService(StatusBar statusBar, NotificationShadeWindowController controller)513 public void setService(StatusBar statusBar, NotificationShadeWindowController controller) { 514 mService = statusBar; 515 mNotificationShadeWindowController = controller; 516 } 517 518 @VisibleForTesting setDragDownHelper(DragDownHelper dragDownHelper)519 void setDragDownHelper(DragDownHelper dragDownHelper) { 520 mDragDownHelper = dragDownHelper; 521 } 522 isIntersecting(View view, float x, float y)523 private boolean isIntersecting(View view, float x, float y) { 524 mTempLocation = view.getLocationOnScreen(); 525 mTempRect.set(mTempLocation[0], mTempLocation[1], mTempLocation[0] + view.getWidth(), 526 mTempLocation[1] + view.getHeight()); 527 return mTempRect.contains(x, y); 528 } 529 } 530