• 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 java.lang.Math.abs;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.AnimatorSet;
23 import android.annotation.Nullable;
24 import android.view.InsetsController;
25 
26 import com.android.launcher3.anim.AnimatedFloat;
27 import com.android.launcher3.taskbar.StashedHandleViewController;
28 import com.android.launcher3.taskbar.TaskbarActivityContext;
29 import com.android.launcher3.taskbar.TaskbarControllers;
30 import com.android.launcher3.taskbar.TaskbarInsetsController;
31 import com.android.launcher3.taskbar.TaskbarStashController;
32 import com.android.launcher3.util.MultiPropertyFactory;
33 
34 /**
35  * Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
36  * create a cohesive animation between stashed/unstashed states.
37  */
38 public class BubbleStashController {
39 
40     private static final String TAG = BubbleStashController.class.getSimpleName();
41 
42     /**
43      * How long to stash/unstash.
44      */
45     public static final long BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
46 
47     /**
48      * The scale bubble bar animates to when being stashed.
49      */
50     private static final float STASHED_BAR_SCALE = 0.5f;
51 
52     protected final TaskbarActivityContext mActivity;
53 
54     // Initialized in init.
55     private TaskbarControllers mControllers;
56     private TaskbarInsetsController mTaskbarInsetsController;
57     private BubbleBarViewController mBarViewController;
58     private BubbleStashedHandleViewController mHandleViewController;
59     private TaskbarStashController mTaskbarStashController;
60 
61     private MultiPropertyFactory.MultiProperty mIconAlphaForStash;
62     private AnimatedFloat mIconScaleForStash;
63     private AnimatedFloat mIconTranslationYForStash;
64     private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha;
65 
66     private boolean mRequestedStashState;
67     private boolean mRequestedExpandedState;
68 
69     private boolean mIsStashed;
70     private int mStashedHeight;
71     private int mUnstashedHeight;
72     private boolean mBubblesShowingOnHome;
73     private boolean mBubblesShowingOnOverview;
74     private boolean mIsSysuiLocked;
75 
76     @Nullable
77     private AnimatorSet mAnimator;
78 
BubbleStashController(TaskbarActivityContext activity)79     public BubbleStashController(TaskbarActivityContext activity) {
80         mActivity = activity;
81     }
82 
init(TaskbarControllers controllers, BubbleControllers bubbleControllers)83     public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
84         mControllers = controllers;
85         mTaskbarInsetsController = controllers.taskbarInsetsController;
86         mBarViewController = bubbleControllers.bubbleBarViewController;
87         mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
88         mTaskbarStashController = controllers.taskbarStashController;
89 
90         mIconAlphaForStash = mBarViewController.getBubbleBarAlpha().get(0);
91         mIconScaleForStash = mBarViewController.getBubbleBarScale();
92         mIconTranslationYForStash = mBarViewController.getBubbleBarTranslationY();
93 
94         mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get(
95                 StashedHandleViewController.ALPHA_INDEX_STASHED);
96 
97         mStashedHeight = mHandleViewController.getStashedHeight();
98         mUnstashedHeight = mHandleViewController.getUnstashedHeight();
99     }
100 
101     /**
102      * Returns the touchable height of the bubble bar based on it's stashed state.
103      */
getTouchableHeight()104     public int getTouchableHeight() {
105         return mIsStashed ? mStashedHeight : mUnstashedHeight;
106     }
107 
108     /**
109      * Returns whether the bubble bar is currently stashed.
110      */
isStashed()111     public boolean isStashed() {
112         return mIsStashed;
113     }
114 
115     /**
116      * Animates the bubble bar and handle to their initial state, transitioning from the state where
117      * both views are invisible. Called when the first bubble is added or when the device is
118      * unlocked.
119      *
120      * <p>Normally either the bubble bar or the handle is visible,
121      * and {@link #showBubbleBar(boolean)} and {@link #stashBubbleBar()} are used to transition
122      * between these two states. But the transition from the state where both the bar and handle
123      * are invisible is slightly different.
124      *
125      * <p>The initial state will depend on the current state of the device, i.e. overview, home etc
126      * and whether bubbles are requested to be expanded.
127      */
animateToInitialState(boolean expanding)128     public void animateToInitialState(boolean expanding) {
129         AnimatorSet animatorSet = new AnimatorSet();
130         if (expanding || mBubblesShowingOnHome || mBubblesShowingOnOverview) {
131             mIsStashed = false;
132             animatorSet.playTogether(mIconScaleForStash.animateToValue(1),
133                     mIconTranslationYForStash.animateToValue(getBubbleBarTranslationY()),
134                     mIconAlphaForStash.animateToValue(1));
135         } else {
136             mIsStashed = true;
137             animatorSet.playTogether(mBubbleStashedHandleAlpha.animateToValue(1));
138         }
139 
140         animatorSet.addListener(new AnimatorListenerAdapter() {
141             @Override
142             public void onAnimationEnd(Animator animation) {
143                 onIsStashedChanged();
144             }
145         });
146         animatorSet.setDuration(BAR_STASH_DURATION).start();
147     }
148 
149     /**
150      * Called when launcher enters or exits the home page. Bubbles are unstashed on home.
151      */
setBubblesShowingOnHome(boolean onHome)152     public void setBubblesShowingOnHome(boolean onHome) {
153         if (mBubblesShowingOnHome != onHome) {
154             mBubblesShowingOnHome = onHome;
155 
156             if (!mBarViewController.hasBubbles()) {
157                 // if there are no bubbles, there's nothing to show, so just return.
158                 return;
159             }
160 
161             if (mBubblesShowingOnHome) {
162                 showBubbleBar(/* expanded= */ false);
163                 // When transitioning from app to home the stash animator may already have been
164                 // created, so we need to animate the bubble bar here to align with hotseat.
165                 if (!mIsStashed) {
166                     mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForHotseat())
167                             .start();
168                 }
169                 // If the bubble bar is already unstashed, the taskbar touchable region won't be
170                 // updated correctly, so force an update here.
171                 mControllers.runAfterInit(() ->
172                         mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
173             } else if (!mBarViewController.isExpanded()) {
174                 stashBubbleBar();
175             }
176         }
177     }
178 
179     /** Whether bubbles are showing on the launcher home page. */
isBubblesShowingOnHome()180     public boolean isBubblesShowingOnHome() {
181         return mBubblesShowingOnHome;
182     }
183 
184     // TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
185     /** Called when launcher enters or exits overview. Bubbles are unstashed on overview. */
setBubblesShowingOnOverview(boolean onOverview)186     public void setBubblesShowingOnOverview(boolean onOverview) {
187         if (mBubblesShowingOnOverview != onOverview) {
188             mBubblesShowingOnOverview = onOverview;
189             if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
190                 stashBubbleBar();
191             } else {
192                 // When transitioning to overview the stash animator may already have been
193                 // created, so we need to animate the bubble bar here to align with taskbar.
194                 mIconTranslationYForStash.animateToValue(getBubbleBarTranslationYForTaskbar())
195                         .start();
196             }
197         }
198     }
199 
200     /** Whether bubbles are showing on Overview. */
isBubblesShowingOnOverview()201     public boolean isBubblesShowingOnOverview() {
202         return mBubblesShowingOnOverview;
203     }
204 
205     /** Called when sysui locked state changes, when locked, bubble bar is stashed. */
onSysuiLockedStateChange(boolean isSysuiLocked)206     public void onSysuiLockedStateChange(boolean isSysuiLocked) {
207         if (isSysuiLocked != mIsSysuiLocked) {
208             mIsSysuiLocked = isSysuiLocked;
209             if (!mIsSysuiLocked && mBarViewController.hasBubbles()) {
210                 animateToInitialState(false /* expanding */);
211             }
212         }
213     }
214 
215     /**
216      * Stashes the bubble bar if allowed based on other state (e.g. on home and overview the
217      * bar does not stash).
218      */
stashBubbleBar()219     public void stashBubbleBar() {
220         mRequestedStashState = true;
221         mRequestedExpandedState = false;
222         updateStashedAndExpandedState();
223     }
224 
225     /**
226      * Shows the bubble bar, and expands bubbles depending on {@param expandBubbles}.
227      */
showBubbleBar(boolean expandBubbles)228     public void showBubbleBar(boolean expandBubbles) {
229         mRequestedStashState = false;
230         mRequestedExpandedState = expandBubbles;
231         updateStashedAndExpandedState();
232     }
233 
updateStashedAndExpandedState()234     private void updateStashedAndExpandedState() {
235         if (mBarViewController.isHiddenForNoBubbles()) {
236             // If there are no bubbles the bar and handle are invisible, nothing to do here.
237             return;
238         }
239         boolean isStashed = mRequestedStashState
240                 && !mBubblesShowingOnHome
241                 && !mBubblesShowingOnOverview;
242         if (mIsStashed != isStashed) {
243             mIsStashed = isStashed;
244             if (mAnimator != null) {
245                 mAnimator.cancel();
246             }
247             mAnimator = createStashAnimator(mIsStashed, BAR_STASH_DURATION);
248             mAnimator.start();
249             onIsStashedChanged();
250         }
251         if (mBarViewController.isExpanded() != mRequestedExpandedState) {
252             mBarViewController.setExpanded(mRequestedExpandedState);
253         }
254     }
255 
256     /**
257      * Create a stash animation.
258      *
259      * @param isStashed whether it's a stash animation or an unstash animation
260      * @param duration duration of the animation
261      * @return the animation
262      */
createStashAnimator(boolean isStashed, long duration)263     private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
264         AnimatorSet animatorSet = new AnimatorSet();
265         final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
266 
267         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
268         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
269         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
270         AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
271 
272         final float firstHalfDurationScale;
273         final float secondHalfDurationScale;
274 
275         if (isStashed) {
276             firstHalfDurationScale = 0.75f;
277             secondHalfDurationScale = 0.5f;
278 
279             fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
280 
281             firstHalfAnimatorSet.playTogether(
282                     mIconAlphaForStash.animateToValue(0),
283                     mIconScaleForStash.animateToValue(STASHED_BAR_SCALE));
284             secondHalfAnimatorSet.playTogether(
285                     mBubbleStashedHandleAlpha.animateToValue(1));
286         } else  {
287             firstHalfDurationScale = 0.5f;
288             secondHalfDurationScale = 0.75f;
289 
290             final float translationY = getBubbleBarTranslationY();
291 
292             fullLengthAnimatorSet.playTogether(
293                     mIconScaleForStash.animateToValue(1),
294                     mIconTranslationYForStash.animateToValue(translationY));
295 
296             firstHalfAnimatorSet.playTogether(
297                     mBubbleStashedHandleAlpha.animateToValue(0)
298             );
299             secondHalfAnimatorSet.playTogether(
300                     mIconAlphaForStash.animateToValue(1)
301             );
302         }
303 
304         fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed));
305 
306         fullLengthAnimatorSet.setDuration(duration);
307         firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
308         secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
309         secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
310 
311         animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
312                 secondHalfAnimatorSet);
313         animatorSet.addListener(new AnimatorListenerAdapter() {
314             @Override
315             public void onAnimationEnd(Animator animation) {
316                 mAnimator = null;
317                 mControllers.runAfterInit(() -> {
318                     if (isStashed) {
319                         mBarViewController.setExpanded(false);
320                     }
321                     mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
322                 });
323             }
324         });
325         return animatorSet;
326     }
327 
onIsStashedChanged()328     private void onIsStashedChanged() {
329         mControllers.runAfterInit(() -> {
330             mHandleViewController.onIsStashedChanged();
331             mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
332         });
333     }
334 
getBubbleBarTranslationYForTaskbar()335     private float getBubbleBarTranslationYForTaskbar() {
336         return -mActivity.getDeviceProfile().taskbarBottomMargin;
337     }
338 
getBubbleBarTranslationYForHotseat()339     private float getBubbleBarTranslationYForHotseat() {
340         final float hotseatBottomSpace = mActivity.getDeviceProfile().hotseatBarBottomSpacePx;
341         final float hotseatCellHeight = mActivity.getDeviceProfile().hotseatCellHeightPx;
342         return -hotseatBottomSpace - hotseatCellHeight + mUnstashedHeight - abs(
343                 hotseatCellHeight - mUnstashedHeight) / 2;
344     }
345 
getBubbleBarTranslationY()346     float getBubbleBarTranslationY() {
347         // If we're on home, adjust the translation so the bubble bar aligns with hotseat.
348         // Otherwise we're either showing in an app or in overview. In either case adjust it so
349         // the bubble bar aligns with the taskbar.
350         return mBubblesShowingOnHome ? getBubbleBarTranslationYForHotseat()
351                 : getBubbleBarTranslationYForTaskbar();
352     }
353 }
354