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