• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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