• 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.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