• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.taskbar.bubbles;
17 
18 import static android.view.View.INVISIBLE;
19 import static android.view.View.VISIBLE;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ValueAnimator;
24 import android.annotation.Nullable;
25 import android.content.res.Resources;
26 import android.graphics.Outline;
27 import android.graphics.Rect;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.view.ViewOutlineProvider;
31 
32 import com.android.launcher3.DeviceProfile;
33 import com.android.launcher3.R;
34 import com.android.launcher3.anim.RevealOutlineAnimation;
35 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
36 import com.android.launcher3.taskbar.StashedHandleView;
37 import com.android.launcher3.taskbar.TaskbarActivityContext;
38 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
39 import com.android.launcher3.util.Executors;
40 import com.android.launcher3.util.MultiPropertyFactory;
41 import com.android.launcher3.util.MultiValueAlpha;
42 import com.android.wm.shell.shared.animation.PhysicsAnimator;
43 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
44 import com.android.wm.shell.shared.handles.RegionSamplingHelper;
45 
46 /**
47  * Handles properties/data collection, then passes the results to our stashed handle View to render.
48  */
49 public class BubbleStashedHandleViewController {
50 
51     private final TaskbarActivityContext mActivity;
52     private final StashedHandleView mStashedHandleView;
53     private final MultiValueAlpha mStashedHandleAlpha;
54     private float mTranslationForSwipeY;
55     private float mTranslationForStashY;
56 
57     // Initialized in init.
58     private BubbleBarViewController mBarViewController;
59     private BubbleStashController mBubbleStashController;
60     private RegionSamplingHelper mRegionSamplingHelper;
61     // Height of the area for the stash handle. Handle will be drawn in the center of this.
62     // This is also the area where touch is handled on the handle.
63     private int mStashedBubbleBarHeight;
64     private int mStashedHandleWidth;
65     private int mStashedHandleHeight;
66 
67     // The bounds of the stashed handle in settled state.
68     private final Rect mStashedHandleBounds = new Rect();
69     private float mStashedHandleRadius;
70 
71     // When the reveal animation is cancelled, we can assume it's about to create a new animation,
72     // which should start off at the same point the cancelled one left off.
73     private float mStartProgressForNextRevealAnim;
74     // Use a nullable boolean to handle initial case where the last animation direction is not known
75     @Nullable
76     private Boolean mWasLastRevealAnimReversed = null;
77 
78     // XXX: if there are more of these maybe do state flags instead
79     private boolean mHiddenForSysui;
80     private boolean mHiddenForNoBubbles;
81     private boolean mHiddenForHomeButtonDisabled;
82 
BubbleStashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)83     public BubbleStashedHandleViewController(TaskbarActivityContext activity,
84             StashedHandleView stashedHandleView) {
85         mActivity = activity;
86         mStashedHandleView = stashedHandleView;
87         mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
88         mStashedHandleAlpha.setUpdateVisibility(true);
89     }
90 
91     /** Initialize controller. */
init(BubbleControllers bubbleControllers)92     public void init(BubbleControllers bubbleControllers) {
93         mBarViewController = bubbleControllers.bubbleBarViewController;
94         mBubbleStashController = bubbleControllers.bubbleStashController;
95 
96         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
97         Resources resources = mActivity.getResources();
98         mStashedHandleHeight = resources.getDimensionPixelSize(
99                 R.dimen.bubblebar_stashed_handle_height);
100         mStashedHandleWidth = resources.getDimensionPixelSize(
101                 R.dimen.bubblebar_stashed_handle_width);
102 
103         int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
104         // Use the max translation for bubble bar whether it is on the home screen or in app.
105         // Use values directly from device profile to avoid referencing other bubble controllers
106         // during init flow.
107         int maxTy = Math.max(deviceProfile.hotseatBarBottomSpacePx,
108                 deviceProfile.taskbarBottomMargin);
109         // Adjust handle view size to accommodate the handle morphing into the bubble bar
110         mStashedHandleView.getLayoutParams().height = barSize + maxTy;
111 
112         mStashedHandleAlpha.get(0).setValue(0);
113 
114         mStashedBubbleBarHeight = resources.getDimensionPixelSize(
115                 R.dimen.bubblebar_stashed_size);
116         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
117             @Override
118             public void getOutline(View view, Outline outline) {
119                 mStashedHandleRadius = view.getHeight() / 2f;
120                 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
121             }
122         });
123 
124         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
125                 new RegionSamplingHelper.SamplingCallback() {
126                     @Override
127                     public void onRegionDarknessChanged(boolean isRegionDark) {
128                         mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
129                     }
130 
131                     @Override
132                     public Rect getSampledRegion(View sampledView) {
133                         return mStashedHandleView.getSampledRegion();
134                     }
135                 }, Executors.MAIN_EXECUTOR, Executors.UI_HELPER_EXECUTOR);
136 
137         mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
138                 updateBounds(mBarViewController.getBubbleBarLocation()));
139     }
140 
141     /** Returns the [PhysicsAnimator] for the stashed handle view. */
getPhysicsAnimator()142     public PhysicsAnimator<View> getPhysicsAnimator() {
143         return PhysicsAnimator.getInstance(mStashedHandleView);
144     }
145 
updateBounds(BubbleBarLocation bubbleBarLocation)146     private void updateBounds(BubbleBarLocation bubbleBarLocation) {
147         // As more bubbles get added, the icon bounds become larger. To ensure a consistent
148         // handle bar position, we pin it to the edge of the screen.
149         final int stashedCenterY = mStashedHandleView.getHeight() - mStashedBubbleBarHeight / 2;
150         final int stashedCenterX;
151         if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
152             final int left = mBarViewController.getHorizontalMargin();
153             stashedCenterX = left + mStashedHandleWidth / 2;
154         } else {
155             final int right =
156                     mStashedHandleView.getRight() - mBarViewController.getHorizontalMargin();
157             stashedCenterX = right - mStashedHandleWidth / 2;
158         }
159         mStashedHandleBounds.set(
160                 stashedCenterX - mStashedHandleWidth / 2,
161                 stashedCenterY - mStashedHandleHeight / 2,
162                 stashedCenterX + mStashedHandleWidth / 2,
163                 stashedCenterY + mStashedHandleHeight / 2
164         );
165         mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
166         mStashedHandleView.setPivotX(stashedCenterX);
167         mStashedHandleView.setPivotY(stashedCenterY);
168     }
169 
onDestroy()170     public void onDestroy() {
171         mRegionSamplingHelper.stopAndDestroy();
172         mRegionSamplingHelper = null;
173     }
174 
175     /**
176      * Returns the width of the stashed handle.
177      */
getStashedWidth()178     public int getStashedWidth() {
179         return mStashedHandleWidth;
180     }
181 
182     /**
183      * Returns the height of the stashed handle.
184      */
getStashedHeight()185     public int getStashedHeight() {
186         return mStashedHandleHeight;
187     }
188 
189     /**
190      * Returns bounds of the stashed handle view
191      */
getBounds(Rect bounds)192     public void getBounds(Rect bounds) {
193         bounds.set(mStashedHandleBounds);
194     }
195 
196     /**
197      * Called when system ui state changes. Bubbles don't show when the device is locked.
198      */
setHiddenForSysui(boolean hidden)199     public void setHiddenForSysui(boolean hidden) {
200         if (mHiddenForSysui != hidden) {
201             mHiddenForSysui = hidden;
202             updateVisibilityForStateChange();
203         }
204     }
205 
206     /**
207      * Called when the handle should be hidden (or shown) because there are no bubbles
208      * (or 1+ bubbles).
209      */
setHiddenForBubbles(boolean hidden)210     public void setHiddenForBubbles(boolean hidden) {
211         if (mHiddenForNoBubbles != hidden) {
212             mHiddenForNoBubbles = hidden;
213             updateVisibilityForStateChange();
214         }
215     }
216 
217     /**
218      * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
219      */
220     // TODO: is this needed for bubbles?
setIsHomeButtonDisabled(boolean homeDisabled)221     public void setIsHomeButtonDisabled(boolean homeDisabled) {
222         mHiddenForHomeButtonDisabled = homeDisabled;
223         updateVisibilityForStateChange();
224     }
225 
226     // TODO: (b/273592694) animate it?
updateVisibilityForStateChange()227     private void updateVisibilityForStateChange() {
228         if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) {
229             mStashedHandleView.setVisibility(VISIBLE);
230         } else {
231             mStashedHandleView.setVisibility(INVISIBLE);
232             mStashedHandleView.setAlpha(0);
233         }
234         updateRegionSampling();
235     }
236 
237     /**
238      * Called when bubble bar is stash state changes so that updates to the stashed handle color
239      * can be started or stopped.
240      */
onIsStashedChanged()241     public void onIsStashedChanged() {
242         updateRegionSampling();
243     }
244 
updateRegionSampling()245     private void updateRegionSampling() {
246         boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
247                 && mBubbleStashController.isStashed();
248         if (mRegionSamplingHelper != null) {
249             mRegionSamplingHelper.setWindowVisible(handleVisible);
250             if (handleVisible) {
251                 mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
252                 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
253             } else {
254                 mRegionSamplingHelper.stop();
255             }
256         }
257     }
258 
259     /**
260      * Sets the translation of the stashed handle during the swipe up gesture.
261      */
setTranslationYForSwipe(float transY)262     public void setTranslationYForSwipe(float transY) {
263         mTranslationForSwipeY = transY;
264         updateTranslationY();
265     }
266 
267     /**
268      * Sets the translation of the stashed handle during the spring on stash animation.
269      */
setTranslationYForStash(float transY)270     public void setTranslationYForStash(float transY) {
271         mTranslationForStashY = transY;
272         updateTranslationY();
273     }
274 
275     /** Sets translation X for stash handle. */
setTranslationX(float translationX)276     public void setTranslationX(float translationX) {
277         mStashedHandleView.setTranslationX(translationX);
278     }
279 
updateTranslationY()280     private void updateTranslationY() {
281         mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
282     }
283 
284     /** Returns the translation of the stashed handle. */
getTranslationY()285     public float getTranslationY() {
286         return mStashedHandleView.getTranslationY();
287     }
288 
289     /**
290      * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
291      */
getStashedHandleAlpha()292     public MultiPropertyFactory<View> getStashedHandleAlpha() {
293         return mStashedHandleAlpha;
294     }
295 
296     /**
297      * Creates and returns an Animator that updates the stashed handle  shape and size.
298      * When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
299      * the size of where the bubble bar icons will be.
300      */
createRevealAnimToIsStashed(boolean isStashed)301     public Animator createRevealAnimToIsStashed(boolean isStashed) {
302         Rect bubbleBarBounds = getLocalBubbleBarBounds();
303 
304         float bubbleBarRadius = bubbleBarBounds.height() / 2f;
305         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
306                 bubbleBarRadius, mStashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
307 
308         boolean isReversed = !isStashed;
309         // We are only changing direction when mWasLastRevealAnimReversed is set at least once
310         boolean changingDirection =
311                 mWasLastRevealAnimReversed != null && mWasLastRevealAnimReversed != isReversed;
312 
313         mWasLastRevealAnimReversed = isReversed;
314         if (changingDirection) {
315             mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
316         }
317 
318         ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
319                 isReversed, mStartProgressForNextRevealAnim);
320         revealAnim.addListener(new AnimatorListenerAdapter() {
321             @Override
322             public void onAnimationEnd(Animator animation) {
323                 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
324             }
325         });
326         return revealAnim;
327     }
328 
329     /**
330      * Get bounds for the bubble bar in the space of the handle view
331      */
getLocalBubbleBarBounds()332     private Rect getLocalBubbleBarBounds() {
333         // Position the bubble bar bounds to the space of handle view
334         Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
335         // Start by moving bubble bar bounds to the bottom of handle view
336         int height = bubbleBarBounds.height();
337         bubbleBarBounds.bottom = mStashedHandleView.getHeight();
338         bubbleBarBounds.top = bubbleBarBounds.bottom - height;
339         // Then apply translation that is applied to the bubble bar
340         bubbleBarBounds.offset(0, (int) mBubbleStashController.getBubbleBarTranslationY());
341         return bubbleBarBounds;
342     }
343 
344     /** Checks that the stash handle is visible and that the motion event is within bounds. */
isEventOverHandle(MotionEvent ev)345     public boolean isEventOverHandle(MotionEvent ev) {
346         if (mStashedHandleView.getVisibility() != VISIBLE) {
347             return false;
348         }
349 
350         // the bounds of the handle only include the visible part, so we check that the Y coordinate
351         // is anywhere within the stashed height of bubble bar (same as taskbar stashed height).
352         final int top = mActivity.getDeviceProfile().heightPx - mStashedBubbleBarHeight;
353         final float x = ev.getRawX();
354         return ev.getRawY() >= top && x >= mStashedHandleBounds.left
355                 && x <= mStashedHandleBounds.right;
356     }
357 
358     /** Set a bubble bar location */
setBubbleBarLocation(BubbleBarLocation bubbleBarLocation)359     public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
360         updateBounds(bubbleBarLocation);
361     }
362 }
363