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