1 /*
2  * Copyright (C) 2021 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;
17 
18 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
19 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
20 import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
21 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
22 
23 import android.animation.Animator;
24 import android.animation.AnimatorListenerAdapter;
25 import android.animation.AnimatorSet;
26 import android.animation.ObjectAnimator;
27 
28 import androidx.annotation.NonNull;
29 
30 import com.android.launcher3.BaseQuickstepLauncher;
31 import com.android.launcher3.LauncherState;
32 import com.android.launcher3.statemanager.StateManager;
33 import com.android.launcher3.util.MultiValueAlpha;
34 import com.android.quickstep.AnimatedFloat;
35 import com.android.quickstep.RecentsAnimationCallbacks;
36 import com.android.quickstep.RecentsAnimationController;
37 import com.android.quickstep.views.RecentsView;
38 import com.android.systemui.shared.recents.model.ThumbnailData;
39 
40 import java.util.HashMap;
41 import java.util.function.Consumer;
42 import java.util.function.Supplier;
43 
44 /**
45  * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
46  * the task bar accordingly.
47  */
48  public class TaskbarLauncherStateController {
49 
50     public static final int FLAG_RESUMED = 1 << 0;
51     public static final int FLAG_RECENTS_ANIMATION_RUNNING = 1 << 1;
52     public static final int FLAG_TRANSITION_STATE_RUNNING = 1 << 2;
53 
54     /** Equivalent to an int with all 1s for binary operation purposes */
55     private static final int FLAGS_ALL = ~0;
56 
57     private final AnimatedFloat mIconAlignmentForResumedState =
58             new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
59     private final AnimatedFloat mIconAlignmentForGestureState =
60             new AnimatedFloat(this::onIconAlignmentRatioChangedForAppAndHomeTransition);
61     private final AnimatedFloat mIconAlignmentForLauncherState =
62             new AnimatedFloat(this::onIconAlignmentRatioChangedForStateTransition);
63 
64     private TaskbarControllers mControllers;
65     private AnimatedFloat mTaskbarBackgroundAlpha;
66     private MultiValueAlpha.AlphaProperty mIconAlphaForHome;
67     private BaseQuickstepLauncher mLauncher;
68 
69     private Integer mPrevState;
70     private int mState;
71     private LauncherState mLauncherState = LauncherState.NORMAL;
72 
73     private boolean mIsAnimatingToLauncherViaGesture;
74     private boolean mIsAnimatingToLauncherViaResume;
75 
76     private final StateManager.StateListener<LauncherState> mStateListener =
77             new StateManager.StateListener<LauncherState>() {
78 
79                 @Override
80                 public void onStateTransitionStart(LauncherState toState) {
81                     if (toState != mLauncherState) {
82                         // Treat FLAG_TRANSITION_STATE_RUNNING as a changed flag even if a previous
83                         // state transition was already running, so we update the new target.
84                         mPrevState &= ~FLAG_TRANSITION_STATE_RUNNING;
85                         mLauncherState = toState;
86                     }
87                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, true);
88                     applyState();
89                 }
90 
91                 @Override
92                 public void onStateTransitionComplete(LauncherState finalState) {
93                     mLauncherState = finalState;
94                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
95                     applyState();
96                 }
97             };
98 
init(TaskbarControllers controllers, BaseQuickstepLauncher launcher)99     public void init(TaskbarControllers controllers, BaseQuickstepLauncher launcher) {
100         mControllers = controllers;
101         mLauncher = launcher;
102 
103         mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
104                 .getTaskbarBackgroundAlpha();
105         MultiValueAlpha taskbarIconAlpha = mControllers.taskbarViewController.getTaskbarIconAlpha();
106         mIconAlphaForHome = taskbarIconAlpha.getProperty(ALPHA_INDEX_HOME);
107         mIconAlphaForHome.setConsumer(
108                 (Consumer<Float>) alpha -> mLauncher.getHotseat().setIconsAlpha(alpha > 0 ? 0 : 1));
109 
110         mIconAlignmentForResumedState.finishAnimation();
111         onIconAlignmentRatioChangedForAppAndHomeTransition();
112 
113         mLauncher.getStateManager().addStateListener(mStateListener);
114 
115         // Initialize to the current launcher state
116         updateStateForFlag(FLAG_RESUMED, launcher.hasBeenResumed());
117         mLauncherState = launcher.getStateManager().getState();
118         applyState(0);
119     }
120 
onDestroy()121     public void onDestroy() {
122         mIconAlignmentForResumedState.finishAnimation();
123         mIconAlignmentForGestureState.finishAnimation();
124         mIconAlignmentForLauncherState.finishAnimation();
125 
126         mIconAlphaForHome.setConsumer(null);
127         mLauncher.getHotseat().setIconsAlpha(1f);
128         mLauncher.getStateManager().removeStateListener(mStateListener);
129     }
130 
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)131     public Animator createAnimToLauncher(@NonNull LauncherState toState,
132             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
133         // If going to overview, stash the task bar
134         // If going home, align the icons to hotseat
135         AnimatorSet animatorSet = new AnimatorSet();
136 
137         // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
138         TaskbarStashController stashController = mControllers.taskbarStashController;
139         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
140                 toState.isTaskbarStashed(mLauncher));
141         stashController.updateStateForFlag(FLAG_IN_APP, false);
142 
143         updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, true);
144         animatorSet.play(stashController.applyStateWithoutStart(duration));
145         animatorSet.play(applyState(duration, false));
146 
147         TaskBarRecentsAnimationListener listener = new TaskBarRecentsAnimationListener(callbacks);
148         callbacks.addListener(listener);
149         RecentsView recentsView = mLauncher.getOverviewPanel();
150         recentsView.setTaskLaunchListener(() -> {
151             listener.endGestureStateOverride(true);
152             callbacks.removeListener(listener);
153         });
154         return animatorSet;
155     }
156 
isAnimatingToLauncher()157     public boolean isAnimatingToLauncher() {
158         return mIsAnimatingToLauncherViaResume || mIsAnimatingToLauncherViaGesture;
159     }
160 
161     /**
162      * Updates the proper flag to change the state of the task bar.
163      *
164      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
165      *
166      * @param flag The flag to update.
167      * @param enabled Whether to enable the flag
168      */
updateStateForFlag(int flag, boolean enabled)169     public void updateStateForFlag(int flag, boolean enabled) {
170         if (enabled) {
171             mState |= flag;
172         } else {
173             mState &= ~flag;
174         }
175     }
176 
hasAnyFlag(int flagMask)177     private boolean hasAnyFlag(int flagMask) {
178         return hasAnyFlag(mState, flagMask);
179     }
180 
hasAnyFlag(int flags, int flagMask)181     private boolean hasAnyFlag(int flags, int flagMask) {
182         return (flags & flagMask) != 0;
183     }
184 
applyState()185     public void applyState() {
186         applyState(TASKBAR_STASH_DURATION);
187     }
188 
applyState(long duration)189     public void applyState(long duration) {
190         applyState(duration, true);
191     }
192 
applyState(boolean start)193     public Animator applyState(boolean start) {
194         return applyState(TASKBAR_STASH_DURATION, start);
195     }
196 
applyState(long duration, boolean start)197     public Animator applyState(long duration, boolean start) {
198         Animator animator = null;
199         if (mPrevState == null || mPrevState != mState) {
200             // If this is our initial state, treat all flags as changed.
201             int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
202             mPrevState = mState;
203             animator = onStateChangeApplied(changedFlags, duration, start);
204         }
205         return animator;
206     }
207 
onStateChangeApplied(int changedFlags, long duration, boolean start)208     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
209         AnimatorSet animatorSet = new AnimatorSet();
210         if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
211             boolean isResumed = isResumed();
212             ObjectAnimator anim = mIconAlignmentForResumedState
213                     .animateToValue(isResumed && goingToUnstashedLauncherState()
214                             ? 1 : 0)
215                     .setDuration(duration);
216 
217             anim.addListener(new AnimatorListenerAdapter() {
218                 @Override
219                 public void onAnimationEnd(Animator animation) {
220                     mIsAnimatingToLauncherViaResume = false;
221                 }
222 
223                 @Override
224                 public void onAnimationStart(Animator animation) {
225                     mIsAnimatingToLauncherViaResume = isResumed;
226 
227                     TaskbarStashController stashController = mControllers.taskbarStashController;
228                     stashController.updateStateForFlag(FLAG_IN_APP, !isResumed);
229                     stashController.applyState(duration);
230                 }
231             });
232             animatorSet.play(anim);
233         }
234 
235         if (hasAnyFlag(changedFlags, FLAG_RECENTS_ANIMATION_RUNNING)) {
236             boolean isRecentsAnimationRunning = isRecentsAnimationRunning();
237             Animator animator = mIconAlignmentForGestureState
238                     .animateToValue(isRecentsAnimationRunning && goingToUnstashedLauncherState()
239                             ? 1 : 0);
240             if (isRecentsAnimationRunning) {
241                 animator.setDuration(duration);
242             }
243             animator.addListener(new AnimatorListenerAdapter() {
244                 @Override
245                 public void onAnimationEnd(Animator animation) {
246                     mIsAnimatingToLauncherViaGesture = false;
247                 }
248 
249                 @Override
250                 public void onAnimationStart(Animator animation) {
251                     mIsAnimatingToLauncherViaGesture = isRecentsAnimationRunning();
252                 }
253             });
254             animatorSet.play(animator);
255         }
256 
257         if (hasAnyFlag(changedFlags, FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING)) {
258             boolean goingToLauncher = hasAnyFlag(FLAG_RESUMED | FLAG_RECENTS_ANIMATION_RUNNING);
259             animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(goingToLauncher ? 0 : 1)
260                     .setDuration(duration));
261         }
262 
263         if (hasAnyFlag(changedFlags, FLAG_TRANSITION_STATE_RUNNING)) {
264             boolean committed = !hasAnyFlag(FLAG_TRANSITION_STATE_RUNNING);
265             playStateTransitionAnim(animatorSet, duration, committed);
266 
267             if (committed && mLauncherState == LauncherState.QUICK_SWITCH) {
268                 // We're about to be paused, set immediately to ensure seamless handoff.
269                 updateStateForFlag(FLAG_RESUMED, false);
270                 applyState(0 /* duration */);
271             }
272         }
273 
274         if (start) {
275             animatorSet.start();
276         }
277         return animatorSet;
278     }
279 
280     /** Returns whether we're going to a state where taskbar icons should align with launcher. */
goingToUnstashedLauncherState()281     private boolean goingToUnstashedLauncherState() {
282         return !mControllers.taskbarStashController.isInStashedLauncherState();
283     }
284 
playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)285     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
286             boolean committed) {
287         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
288         float toAlignment = mLauncherState.isTaskbarAlignedWithHotseat(mLauncher) ? 1 : 0;
289 
290         TaskbarStashController controller = mControllers.taskbarStashController;
291         controller.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
292         Animator stashAnimator = controller.applyStateWithoutStart(duration);
293         if (stashAnimator != null) {
294             stashAnimator.addListener(new AnimatorListenerAdapter() {
295                 @Override
296                 public void onAnimationEnd(Animator animation) {
297                     if (isInStashedState && committed) {
298                         // Reset hotseat alpha to default
299                         mLauncher.getHotseat().setIconsAlpha(1);
300                     }
301                 }
302 
303                 @Override
304                 public void onAnimationStart(Animator animation) {
305                     if (mLauncher.getHotseat().getIconsAlpha() > 0) {
306                         mIconAlphaForHome.setValue(mLauncher.getHotseat().getIconsAlpha());
307                     }
308                 }
309             });
310             animatorSet.play(stashAnimator);
311         }
312 
313         animatorSet.play(mIconAlignmentForLauncherState.animateToValue(toAlignment)
314                 .setDuration(duration));
315     }
316 
isResumed()317     private boolean isResumed() {
318         return (mState & FLAG_RESUMED) != 0;
319     }
320 
isRecentsAnimationRunning()321     private boolean isRecentsAnimationRunning() {
322         return (mState & FLAG_RECENTS_ANIMATION_RUNNING) != 0;
323     }
324 
onIconAlignmentRatioChangedForStateTransition()325     private void onIconAlignmentRatioChangedForStateTransition() {
326         if (!isResumed()) {
327             return;
328         }
329         onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioForLauncherState);
330     }
331 
onIconAlignmentRatioChangedForAppAndHomeTransition()332     private void onIconAlignmentRatioChangedForAppAndHomeTransition() {
333         onIconAlignmentRatioChanged(this::getCurrentIconAlignmentRatioBetweenAppAndHome);
334     }
335 
onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier)336     private void onIconAlignmentRatioChanged(Supplier<Float> alignmentSupplier) {
337         if (mControllers == null) {
338             return;
339         }
340         float alignment = alignmentSupplier.get();
341         mControllers.taskbarViewController.setLauncherIconAlignment(
342                 alignment, mLauncher.getDeviceProfile());
343 
344         // Switch taskbar and hotseat in last frame
345         setTaskbarViewVisible(alignment < 1);
346     }
347 
348     private float getCurrentIconAlignmentRatioBetweenAppAndHome() {
349         return Math.max(mIconAlignmentForResumedState.value, mIconAlignmentForGestureState.value);
350     }
351 
352     private float getCurrentIconAlignmentRatioForLauncherState() {
353         return mIconAlignmentForLauncherState.value;
354     }
355 
356     private void setTaskbarViewVisible(boolean isVisible) {
357         mIconAlphaForHome.setValue(isVisible ? 1 : 0);
358     }
359 
360     private final class TaskBarRecentsAnimationListener implements
361             RecentsAnimationCallbacks.RecentsAnimationListener {
362         private final RecentsAnimationCallbacks mCallbacks;
363 
364         TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
365             mCallbacks = callbacks;
366         }
367 
368         @Override
369         public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
370             endGestureStateOverride(true);
371         }
372 
373         @Override
374         public void onRecentsAnimationFinished(RecentsAnimationController controller) {
375             endGestureStateOverride(!controller.getFinishTargetIsLauncher());
376         }
377 
378         private void endGestureStateOverride(boolean finishedToApp) {
379             mCallbacks.removeListener(this);
380 
381             // Update the resumed state immediately to ensure a seamless handoff
382             boolean launcherResumed = !finishedToApp;
383             updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
384             updateStateForFlag(FLAG_RESUMED, launcherResumed);
385             applyState();
386             // Set this last because applyState() might also animate it.
387             mIconAlignmentForResumedState.cancelAnimation();
388             mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
389 
390             TaskbarStashController controller = mControllers.taskbarStashController;
391             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
392             controller.applyState();
393         }
394     }
395 }
396