• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.dreams.touch;
18 
19 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
20 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
21 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.ValueAnimator;
26 import android.graphics.Rect;
27 import android.graphics.Region;
28 import android.util.Log;
29 import android.view.GestureDetector;
30 import android.view.InputEvent;
31 import android.view.MotionEvent;
32 import android.view.VelocityTracker;
33 
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.internal.logging.UiEvent;
37 import com.android.internal.logging.UiEventLogger;
38 import com.android.systemui.dreams.touch.scrim.ScrimController;
39 import com.android.systemui.dreams.touch.scrim.ScrimManager;
40 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
41 import com.android.systemui.shade.ShadeExpansionChangeEvent;
42 import com.android.systemui.statusbar.NotificationShadeWindowController;
43 import com.android.systemui.statusbar.phone.CentralSurfaces;
44 import com.android.wm.shell.animation.FlingAnimationUtils;
45 
46 import java.util.Optional;
47 
48 import javax.inject.Inject;
49 import javax.inject.Named;
50 
51 /**
52  * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
53  */
54 public class BouncerSwipeTouchHandler implements DreamTouchHandler {
55     /**
56      * An interface for creating ValueAnimators.
57      */
58     public interface ValueAnimatorCreator {
59         /**
60          * Creates {@link ValueAnimator}.
61          */
create(float start, float finish)62         ValueAnimator create(float start, float finish);
63     }
64 
65     /**
66      * An interface for obtaining VelocityTrackers.
67      */
68     public interface VelocityTrackerFactory {
69         /**
70          * Obtains {@link VelocityTracker}.
71          */
obtain()72         VelocityTracker obtain();
73     }
74 
75     public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
76 
77     private static final String TAG = "BouncerSwipeTouchHandler";
78     private final NotificationShadeWindowController mNotificationShadeWindowController;
79     private final float mBouncerZoneScreenPercentage;
80 
81     private final ScrimManager mScrimManager;
82     private ScrimController mCurrentScrimController;
83     private float mCurrentExpansion;
84     private final Optional<CentralSurfaces> mCentralSurfaces;
85 
86     private VelocityTracker mVelocityTracker;
87 
88     private final FlingAnimationUtils mFlingAnimationUtils;
89     private final FlingAnimationUtils mFlingAnimationUtilsClosing;
90 
91     private Boolean mCapture;
92     private Boolean mExpanded;
93 
94     private boolean mBouncerInitiallyShowing;
95 
96     private TouchSession mTouchSession;
97 
98     private ValueAnimatorCreator mValueAnimatorCreator;
99 
100     private VelocityTrackerFactory mVelocityTrackerFactory;
101 
102     private final UiEventLogger mUiEventLogger;
103 
104     private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
105         @Override
106         public void onScrimControllerChanged(ScrimController controller) {
107             if (mCurrentScrimController != null) {
108                 mCurrentScrimController.reset();
109             }
110 
111             mCurrentScrimController = controller;
112         }
113     };
114 
115     private final GestureDetector.OnGestureListener mOnGestureListener =
116             new GestureDetector.SimpleOnGestureListener() {
117                 @Override
118                 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
119                         float distanceY) {
120                     if (mCapture == null) {
121                         // If the user scrolling favors a vertical direction, begin capturing
122                         // scrolls.
123                         mCapture = Math.abs(distanceY) > Math.abs(distanceX);
124                         mBouncerInitiallyShowing = mCentralSurfaces
125                                 .map(CentralSurfaces::isBouncerShowing)
126                                 .orElse(false);
127 
128                         if (mCapture) {
129                             // reset expanding
130                             mExpanded = false;
131                             // Since the user is dragging the bouncer up, set scrimmed to false.
132                             mCurrentScrimController.show();
133                         }
134                     }
135 
136                     if (!mCapture) {
137                         return false;
138                     }
139 
140                     // Don't set expansion for downward scroll when the bouncer is hidden.
141                     if (!mBouncerInitiallyShowing && (e1.getY() < e2.getY())) {
142                         return true;
143                     }
144 
145                     // Don't set expansion for upward scroll when the bouncer is shown.
146                     if (mBouncerInitiallyShowing && (e1.getY() > e2.getY())) {
147                         return true;
148                     }
149 
150                     if (!mCentralSurfaces.isPresent()) {
151                         return true;
152                     }
153 
154                     // For consistency, we adopt the expansion definition found in the
155                     // PanelViewController. In this case, expansion refers to the view above the
156                     // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
157                     // is fully hidden at full expansion (1) and fully visible when fully collapsed
158                     // (0).
159                     final float dragDownAmount = e2.getY() - e1.getY();
160                     final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
161                             / mTouchSession.getBounds().height();
162                     setPanelExpansion(mBouncerInitiallyShowing
163                             ? screenTravelPercentage : 1 - screenTravelPercentage, dragDownAmount);
164                     return true;
165                 }
166             };
167 
setPanelExpansion(float expansion, float dragDownAmount)168     private void setPanelExpansion(float expansion, float dragDownAmount) {
169         mCurrentExpansion = expansion;
170         ShadeExpansionChangeEvent event =
171                 new ShadeExpansionChangeEvent(
172                         /* fraction= */ mCurrentExpansion,
173                         /* expanded= */ mExpanded,
174                         /* tracking= */ true,
175                         /* dragDownPxAmount= */ dragDownAmount);
176         mCurrentScrimController.expand(event);
177     }
178 
179 
180     @VisibleForTesting
181     public enum DreamEvent implements UiEventLogger.UiEventEnum {
182         @UiEvent(doc = "The screensaver has been swiped up.")
183         DREAM_SWIPED(988),
184 
185         @UiEvent(doc = "The bouncer has become fully visible over dream.")
186         DREAM_BOUNCER_FULLY_VISIBLE(1056);
187 
188         private final int mId;
189 
DreamEvent(int id)190         DreamEvent(int id) {
191             mId = id;
192         }
193 
194         @Override
getId()195         public int getId() {
196             return mId;
197         }
198     }
199 
200     @Inject
BouncerSwipeTouchHandler( ScrimManager scrimManager, Optional<CentralSurfaces> centralSurfaces, NotificationShadeWindowController notificationShadeWindowController, ValueAnimatorCreator valueAnimatorCreator, VelocityTrackerFactory velocityTrackerFactory, @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING) FlingAnimationUtils flingAnimationUtils, @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) FlingAnimationUtils flingAnimationUtilsClosing, @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage, UiEventLogger uiEventLogger)201     public BouncerSwipeTouchHandler(
202             ScrimManager scrimManager,
203             Optional<CentralSurfaces> centralSurfaces,
204             NotificationShadeWindowController notificationShadeWindowController,
205             ValueAnimatorCreator valueAnimatorCreator,
206             VelocityTrackerFactory velocityTrackerFactory,
207             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
208                     FlingAnimationUtils flingAnimationUtils,
209             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
210                     FlingAnimationUtils flingAnimationUtilsClosing,
211             @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
212             UiEventLogger uiEventLogger) {
213         mCentralSurfaces = centralSurfaces;
214         mScrimManager = scrimManager;
215         mNotificationShadeWindowController = notificationShadeWindowController;
216         mBouncerZoneScreenPercentage = swipeRegionPercentage;
217         mFlingAnimationUtils = flingAnimationUtils;
218         mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
219         mValueAnimatorCreator = valueAnimatorCreator;
220         mVelocityTrackerFactory = velocityTrackerFactory;
221         mUiEventLogger = uiEventLogger;
222     }
223 
224     @Override
getTouchInitiationRegion(Rect bounds, Region region)225     public void getTouchInitiationRegion(Rect bounds, Region region) {
226         final int width = bounds.width();
227         final int height = bounds.height();
228 
229         if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
230             region.op(new Rect(0, 0, width,
231                             Math.round(
232                                     height * mBouncerZoneScreenPercentage)),
233                     Region.Op.UNION);
234         } else {
235             region.op(new Rect(0,
236                             Math.round(height * (1 - mBouncerZoneScreenPercentage)),
237                             width,
238                             height),
239                     Region.Op.UNION);
240         }
241     }
242 
243     @Override
onSessionStart(TouchSession session)244     public void onSessionStart(TouchSession session) {
245         mVelocityTracker = mVelocityTrackerFactory.obtain();
246         mTouchSession = session;
247         mVelocityTracker.clear();
248         mNotificationShadeWindowController.setForcePluginOpen(true, this);
249         mScrimManager.addCallback(mScrimManagerCallback);
250         mCurrentScrimController = mScrimManager.getCurrentController();
251 
252         session.registerCallback(() -> {
253             if (mVelocityTracker != null) {
254                 mVelocityTracker.recycle();
255                 mVelocityTracker = null;
256             }
257             mScrimManager.removeCallback(mScrimManagerCallback);
258             mCapture = null;
259             mNotificationShadeWindowController.setForcePluginOpen(false, this);
260         });
261 
262         session.registerGestureListener(mOnGestureListener);
263         session.registerInputListener(ev -> onMotionEvent(ev));
264 
265     }
266 
onMotionEvent(InputEvent event)267     private void onMotionEvent(InputEvent event) {
268         if (!(event instanceof MotionEvent)) {
269             Log.e(TAG, "non MotionEvent received:" + event);
270             return;
271         }
272 
273         final MotionEvent motionEvent = (MotionEvent) event;
274 
275         switch (motionEvent.getAction()) {
276             case MotionEvent.ACTION_CANCEL:
277             case MotionEvent.ACTION_UP:
278                 mTouchSession.pop();
279                 // If we are not capturing any input, there is no need to consider animating to
280                 // finish transition.
281                 if (mCapture == null || !mCapture) {
282                     break;
283                 }
284 
285                 // We must capture the resulting velocities as resetMonitor() will clear these
286                 // values.
287                 mVelocityTracker.computeCurrentVelocity(1000);
288                 final float verticalVelocity = mVelocityTracker.getYVelocity();
289                 final float horizontalVelocity = mVelocityTracker.getXVelocity();
290 
291                 final float velocityVector =
292                         (float) Math.hypot(horizontalVelocity, verticalVelocity);
293 
294                 mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
295                 final float expansion = mExpanded
296                         ? KeyguardBouncerConstants.EXPANSION_VISIBLE
297                         : KeyguardBouncerConstants.EXPANSION_HIDDEN;
298 
299                 // Log the swiping up to show Bouncer event.
300                 if (!mBouncerInitiallyShowing
301                         && expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
302                     mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
303                 }
304 
305                 flingToExpansion(verticalVelocity, expansion);
306 
307                 if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
308                     mCurrentScrimController.reset();
309                 }
310                 break;
311             default:
312                 mVelocityTracker.addMovement(motionEvent);
313                 break;
314         }
315     }
316 
createExpansionAnimator(float targetExpansion, float expansionHeight)317     private ValueAnimator createExpansionAnimator(float targetExpansion, float expansionHeight) {
318         final ValueAnimator animator =
319                 mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
320         animator.addUpdateListener(
321                 animation -> {
322                     float expansionFraction = (float) animation.getAnimatedValue();
323                     float dragDownAmount = expansionFraction * expansionHeight;
324                     setPanelExpansion(expansionFraction, dragDownAmount);
325                 });
326         if (!mBouncerInitiallyShowing
327                 && targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
328             animator.addListener(
329                     new AnimatorListenerAdapter() {
330                         @Override
331                         public void onAnimationEnd(Animator animation) {
332                             mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
333                         }
334                     });
335         }
336         return animator;
337     }
338 
flingRevealsOverlay(float velocity, float velocityVector)339     protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
340         // Fully expand the space above the bouncer, if the user has expanded the bouncer less
341         // than halfway or final velocity was positive, indicating a downward direction.
342         if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
343             return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
344         } else {
345             return velocity > 0;
346         }
347     }
348 
flingToExpansion(float velocity, float expansion)349     protected void flingToExpansion(float velocity, float expansion) {
350         if (!mCentralSurfaces.isPresent()) {
351             return;
352         }
353 
354         // The animation utils deal in pixel units, rather than expansion height.
355         final float viewHeight = mTouchSession.getBounds().height();
356         final float currentHeight = viewHeight * mCurrentExpansion;
357         final float targetHeight = viewHeight * expansion;
358         final float expansionHeight = targetHeight - currentHeight;
359         final ValueAnimator animator = createExpansionAnimator(expansion, expansionHeight);
360         if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
361             // Hides the bouncer, i.e., fully expands the space above the bouncer.
362             mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
363                     viewHeight);
364         } else {
365             // Shows the bouncer, i.e., fully collapses the space above the bouncer.
366             mFlingAnimationUtils.apply(
367                     animator, currentHeight, targetHeight, velocity, viewHeight);
368         }
369 
370         animator.start();
371     }
372 }
373