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.content.res.Resources;
25 import android.graphics.Outline;
26 import android.graphics.Rect;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.ViewOutlineProvider;
30 
31 import com.android.launcher3.R;
32 import com.android.launcher3.anim.RevealOutlineAnimation;
33 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
34 import com.android.launcher3.taskbar.StashedHandleView;
35 import com.android.launcher3.taskbar.TaskbarActivityContext;
36 import com.android.launcher3.taskbar.TaskbarControllers;
37 import com.android.launcher3.util.Executors;
38 import com.android.launcher3.util.MultiPropertyFactory;
39 import com.android.launcher3.util.MultiValueAlpha;
40 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
41 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
42 import com.android.wm.shell.shared.animation.PhysicsAnimator;
43 
44 /**
45  * Handles properties/data collection, then passes the results to our stashed handle View to render.
46  */
47 public class BubbleStashedHandleViewController {
48 
49     private final TaskbarActivityContext mActivity;
50     private final StashedHandleView mStashedHandleView;
51     private final MultiValueAlpha mStashedHandleAlpha;
52 
53     // Initialized in init.
54     private BubbleBarViewController mBarViewController;
55     private BubbleStashController mBubbleStashController;
56     private RegionSamplingHelper mRegionSamplingHelper;
57     private int mBarSize;
58     private int mStashedTaskbarHeight;
59     private int mStashedHandleWidth;
60     private int mStashedHandleHeight;
61 
62     // The bounds we want to clip to in the settled state when showing the stashed handle.
63     private final Rect mStashedHandleBounds = new Rect();
64 
65     // When the reveal animation is cancelled, we can assume it's about to create a new animation,
66     // which should start off at the same point the cancelled one left off.
67     private float mStartProgressForNextRevealAnim;
68     private boolean mWasLastRevealAnimReversed;
69 
70     // XXX: if there are more of these maybe do state flags instead
71     private boolean mHiddenForSysui;
72     private boolean mHiddenForNoBubbles;
73     private boolean mHiddenForHomeButtonDisabled;
74 
BubbleStashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)75     public BubbleStashedHandleViewController(TaskbarActivityContext activity,
76             StashedHandleView stashedHandleView) {
77         mActivity = activity;
78         mStashedHandleView = stashedHandleView;
79         mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
80     }
81 
init(TaskbarControllers controllers, BubbleControllers bubbleControllers)82     public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
83         mBarViewController = bubbleControllers.bubbleBarViewController;
84         mBubbleStashController = bubbleControllers.bubbleStashController;
85 
86         Resources resources = mActivity.getResources();
87         mStashedHandleHeight = resources.getDimensionPixelSize(
88                 R.dimen.bubblebar_stashed_handle_height);
89         mStashedHandleWidth = resources.getDimensionPixelSize(
90                 R.dimen.bubblebar_stashed_handle_width);
91         mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
92 
93         final int bottomMargin = resources.getDimensionPixelSize(
94                 R.dimen.transient_taskbar_bottom_margin);
95         mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
96 
97         mStashedHandleAlpha.get(0).setValue(0);
98 
99         mStashedTaskbarHeight = resources.getDimensionPixelSize(
100                 R.dimen.bubblebar_stashed_size);
101         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
102             @Override
103             public void getOutline(View view, Outline outline) {
104                 float stashedHandleRadius = view.getHeight() / 2f;
105                 outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius);
106             }
107         });
108 
109         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
110                 new RegionSamplingHelper.SamplingCallback() {
111                     @Override
112                     public void onRegionDarknessChanged(boolean isRegionDark) {
113                         mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
114                     }
115 
116                     @Override
117                     public Rect getSampledRegion(View sampledView) {
118                         return mStashedHandleView.getSampledRegion();
119                     }
120                 }, Executors.UI_HELPER_EXECUTOR);
121 
122         mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
123                 updateBounds(mBarViewController.getBubbleBarLocation()));
124     }
125 
126     /** Returns the [PhysicsAnimator] for the stashed handle view. */
getPhysicsAnimator()127     public PhysicsAnimator<View> getPhysicsAnimator() {
128         return PhysicsAnimator.getInstance(mStashedHandleView);
129     }
130 
updateBounds(BubbleBarLocation bubbleBarLocation)131     private void updateBounds(BubbleBarLocation bubbleBarLocation) {
132         // As more bubbles get added, the icon bounds become larger. To ensure a consistent
133         // handle bar position, we pin it to the edge of the screen.
134         final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
135         if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
136             final int left = mBarViewController.getHorizontalMargin();
137             mStashedHandleBounds.set(
138                     left,
139                     stashedCenterY - mStashedHandleHeight / 2,
140                     left + mStashedHandleWidth,
141                     stashedCenterY + mStashedHandleHeight / 2);
142             mStashedHandleView.setPivotX(0);
143         } else {
144             final int right =
145                     mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
146             mStashedHandleBounds.set(
147                     right - mStashedHandleWidth,
148                     stashedCenterY - mStashedHandleHeight / 2,
149                     right,
150                     stashedCenterY + mStashedHandleHeight / 2);
151             mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
152         }
153 
154         mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
155         mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
156     }
157 
onDestroy()158     public void onDestroy() {
159         mRegionSamplingHelper.stopAndDestroy();
160         mRegionSamplingHelper = null;
161     }
162 
163     /**
164      * Returns the height of the stashed handle.
165      */
getStashedHeight()166     public int getStashedHeight() {
167         return mStashedHandleHeight;
168     }
169 
170     /**
171      * Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
172      */
getUnstashedHeight()173     public int getUnstashedHeight() {
174         return mBarSize;
175     }
176 
177     /**
178      * Called when system ui state changes. Bubbles don't show when the device is locked.
179      */
setHiddenForSysui(boolean hidden)180     public void setHiddenForSysui(boolean hidden) {
181         if (mHiddenForSysui != hidden) {
182             mHiddenForSysui = hidden;
183             updateVisibilityForStateChange();
184         }
185     }
186 
187     /**
188      * Called when the handle should be hidden (or shown) because there are no bubbles
189      * (or 1+ bubbles).
190      */
setHiddenForBubbles(boolean hidden)191     public void setHiddenForBubbles(boolean hidden) {
192         if (mHiddenForNoBubbles != hidden) {
193             mHiddenForNoBubbles = hidden;
194             updateVisibilityForStateChange();
195         }
196     }
197 
198     /**
199      * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
200      */
201     // TODO: is this needed for bubbles?
setIsHomeButtonDisabled(boolean homeDisabled)202     public void setIsHomeButtonDisabled(boolean homeDisabled) {
203         mHiddenForHomeButtonDisabled = homeDisabled;
204         updateVisibilityForStateChange();
205     }
206 
207     // TODO: (b/273592694) animate it?
updateVisibilityForStateChange()208     private void updateVisibilityForStateChange() {
209         if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) {
210             mStashedHandleView.setVisibility(VISIBLE);
211         } else {
212             mStashedHandleView.setVisibility(INVISIBLE);
213             mStashedHandleView.setAlpha(0);
214         }
215         updateRegionSampling();
216     }
217 
218     /**
219      * Called when bubble bar is stash state changes so that updates to the stashed handle color
220      * can be started or stopped.
221      */
onIsStashedChanged()222     public void onIsStashedChanged() {
223         updateRegionSampling();
224     }
225 
updateRegionSampling()226     private void updateRegionSampling() {
227         boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
228                 && mBubbleStashController.isStashed();
229         if (mRegionSamplingHelper != null) {
230             mRegionSamplingHelper.setWindowVisible(handleVisible);
231             if (handleVisible) {
232                 mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
233                 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
234             } else {
235                 mRegionSamplingHelper.stop();
236             }
237         }
238     }
239 
240     /**
241      * Sets the translation of the stashed handle during the swipe up gesture.
242      */
setTranslationYForSwipe(float transY)243     public void setTranslationYForSwipe(float transY) {
244         mStashedHandleView.setTranslationY(transY);
245     }
246 
247     /**
248      * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
249      */
getStashedHandleAlpha()250     public MultiPropertyFactory<View> getStashedHandleAlpha() {
251         return mStashedHandleAlpha;
252     }
253 
254     /**
255      * Creates and returns an Animator that updates the stashed handle  shape and size.
256      * When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
257      * the size of where the bubble bar icons will be.
258      */
createRevealAnimToIsStashed(boolean isStashed)259     public Animator createRevealAnimToIsStashed(boolean isStashed) {
260         Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
261 
262         // Account for the full visual height of the bubble bar
263         int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2;
264         bubbleBarBounds.top -= heightDiff;
265         bubbleBarBounds.bottom += heightDiff;
266         float stashedHandleRadius = mStashedHandleView.getHeight() / 2f;
267         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
268                 stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
269 
270         boolean isReversed = !isStashed;
271         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
272         mWasLastRevealAnimReversed = isReversed;
273         if (changingDirection) {
274             mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
275         }
276 
277         ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
278                 isReversed, mStartProgressForNextRevealAnim);
279         revealAnim.addListener(new AnimatorListenerAdapter() {
280             @Override
281             public void onAnimationEnd(Animator animation) {
282                 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
283             }
284         });
285         return revealAnim;
286     }
287 
288     /** Checks that the stash handle is visible and that the motion event is within bounds. */
isEventOverHandle(MotionEvent ev)289     public boolean isEventOverHandle(MotionEvent ev) {
290         if (mStashedHandleView.getVisibility() != VISIBLE) {
291             return false;
292         }
293 
294         // the bounds of the handle only include the visible part, so we check that the Y coordinate
295         // is anywhere within the stashed taskbar height.
296         int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
297 
298         return (int) ev.getRawY() >= top && containsX((int) ev.getRawX());
299     }
300 
301     /** Checks if the given x coordinate is within the stashed handle bounds. */
containsX(int x)302     public boolean containsX(int x) {
303         return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
304     }
305 
306     /** Set a bubble bar location */
setBubbleBarLocation(BubbleBarLocation bubbleBarLocation)307     public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
308         updateBounds(bubbleBarLocation);
309     }
310 }
311