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