• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.animation.Interpolators.EMPHASIZED;
19 import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
20 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
21 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
22 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
23 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
24 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
28 import static com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE;
29 
30 import android.animation.Animator;
31 import android.animation.AnimatorListenerAdapter;
32 import android.animation.AnimatorSet;
33 import android.animation.ObjectAnimator;
34 import android.os.SystemClock;
35 import android.util.Log;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 
40 import com.android.launcher3.AbstractFloatingView;
41 import com.android.launcher3.DeviceProfile;
42 import com.android.launcher3.LauncherState;
43 import com.android.launcher3.QuickstepTransitionManager;
44 import com.android.launcher3.Utilities;
45 import com.android.launcher3.anim.AnimatedFloat;
46 import com.android.launcher3.anim.AnimatorListeners;
47 import com.android.launcher3.statemanager.StateManager;
48 import com.android.launcher3.uioverrides.QuickstepLauncher;
49 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
50 import com.android.quickstep.RecentsAnimationCallbacks;
51 import com.android.quickstep.RecentsAnimationController;
52 import com.android.quickstep.views.RecentsView;
53 import com.android.systemui.animation.ViewRootSync;
54 import com.android.systemui.shared.recents.model.ThumbnailData;
55 
56 import java.io.PrintWriter;
57 import java.util.HashMap;
58 import java.util.StringJoiner;
59 
60 /**
61  * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
62  * the task bar accordingly.
63  */
64 public class TaskbarLauncherStateController {
65 
66     private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
67     private static final boolean DEBUG = false;
68 
69     /** Launcher activity is resumed and focused. */
70     public static final int FLAG_RESUMED = 1 << 0;
71 
72     /**
73      * A external transition / animation is running that will result in FLAG_RESUMED being set.
74      **/
75     public static final int FLAG_TRANSITION_TO_RESUMED = 1 << 1;
76 
77     /**
78      * Set while the launcher state machine is performing a state transition, see {@link
79      * StateManager.StateListener}.
80      */
81     public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2;
82 
83     /**
84      * Whether the screen is currently on, or is transitioning to be on.
85      *
86      * This is cleared as soon as the screen begins to transition off.
87      */
88     private static final int FLAG_AWAKE = 1 << 3;
89 
90     /**
91      * Captures whether the launcher was active at the time the FLAG_AWAKE was cleared.
92      * Always cleared when FLAG_AWAKE is set.
93      * <p>
94      * FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this
95      * point. Thus, this flag indicates whether the launcher will be shown when the device wakes up
96      * again.
97      */
98     private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4;
99 
100     /**
101      * Whether the device is currently locked.
102      * <ul>
103      *  <li>While locked, the taskbar is always stashed.<li/>
104      *  <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li>
105      * </ul>
106      */
107     private static final int FLAG_DEVICE_LOCKED = 1 << 5;
108 
109     /**
110      * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed).
111      * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by
112      * hiding the inset.
113      *
114      * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible
115      * resting state while hidden is stashed.
116      */
117     private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
118 
119     private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_RESUMED | FLAG_TRANSITION_TO_RESUMED;
120     /** Equivalent to an int with all 1s for binary operation purposes */
121     private static final int FLAGS_ALL = ~0;
122 
123     private static final float TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f;
124     private static final float TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f;
125     private static final float TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT = 0.25f;
126 
127     /**
128      * Delay for the taskbar fade-in.
129      *
130      * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions
131      * to launcher directly. The delay avoids the navbar to become briefly visible. The duration
132      * is the same as in SysUI, see http://shortn/_uNSbDoRUSr.
133      */
134     private static final long TASKBAR_SHOW_DELAY_MS = 250;
135 
136     private final AnimatedFloat mIconAlignment =
137             new AnimatedFloat(this::onIconAlignmentRatioChanged);
138 
139     private TaskbarControllers mControllers;
140     private AnimatedFloat mTaskbarBackgroundAlpha;
141     private AnimatedFloat mTaskbarAlpha;
142     private AnimatedFloat mTaskbarCornerRoundness;
143     private MultiProperty mIconAlphaForHome;
144     private QuickstepLauncher mLauncher;
145 
146     private Integer mPrevState;
147     private int mState;
148     private LauncherState mLauncherState = LauncherState.NORMAL;
149 
150     // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
151     private long mLastUnlockTimeMs = 0;
152 
153     private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
154 
155     private boolean mIsAnimatingToLauncher;
156 
157     private boolean mShouldDelayLauncherStateAnim;
158 
159     // We skip any view synchronizations during init/destroy.
160     private boolean mCanSyncViews;
161 
162     private boolean mIsQsbInline;
163 
164     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
165             new DeviceProfile.OnDeviceProfileChangeListener() {
166                 @Override
167                 public void onDeviceProfileChanged(DeviceProfile dp) {
168                     if (mIsQsbInline && !dp.isQsbInline) {
169                         // We only modify QSB alpha if isQsbInline = true. If we switch to a DP
170                         // where isQsbInline = false, then we need to reset the alpha.
171                         mLauncher.getHotseat().setQsbAlpha(1f);
172                     }
173                     mIsQsbInline = dp.isQsbInline;
174                     TaskbarLauncherStateController.this.updateIconAlphaForHome(
175                             mIconAlphaForHome.getValue());
176                 }
177             };
178 
179     private final StateManager.StateListener<LauncherState> mStateListener =
180             new StateManager.StateListener<LauncherState>() {
181 
182                 @Override
183                 public void onStateTransitionStart(LauncherState toState) {
184                     if (toState != mLauncherState) {
185                         // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
186                         // previous state transition was already running, so we update the new
187                         // target.
188                         mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
189                         mLauncherState = toState;
190                     }
191                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
192                     if (!mShouldDelayLauncherStateAnim) {
193                         if (toState == LauncherState.NORMAL) {
194                             applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION);
195                         } else {
196                             applyState();
197                         }
198                     }
199                 }
200 
201                 @Override
202                 public void onStateTransitionComplete(LauncherState finalState) {
203                     mLauncherState = finalState;
204                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
205                     // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
206                     // taskbar on top of transparent activity.
207                     if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) {
208                         updateStateForFlag(FLAG_RESUMED, true);
209                     }
210                     applyState();
211                     boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
212                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
213                             mControllers, finalState.disallowTaskbarGlobalDrag(),
214                             disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
215                 }
216             };
217 
218     /** Initializes the controller instance, and applies the initial state immediately. */
init(TaskbarControllers controllers, QuickstepLauncher launcher, int sysuiStateFlags)219     public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
220             int sysuiStateFlags) {
221         mCanSyncViews = false;
222 
223         mControllers = controllers;
224         mLauncher = launcher;
225 
226         mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline;
227 
228         mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
229                 .getTaskbarBackgroundAlpha();
230         mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha();
231         mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness();
232         mIconAlphaForHome = mControllers.taskbarViewController
233                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
234 
235         resetIconAlignment();
236 
237         mLauncher.getStateManager().addStateListener(mStateListener);
238         mLauncherState = launcher.getStateManager().getState();
239         updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
240 
241         applyState(0);
242 
243         mCanSyncViews = true;
244         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
245     }
246 
onDestroy()247     public void onDestroy() {
248         mCanSyncViews = false;
249 
250         mIconAlignment.finishAnimation();
251 
252         mLauncher.getHotseat().setIconsAlpha(1f);
253         mLauncher.getStateManager().removeStateListener(mStateListener);
254 
255         mCanSyncViews = true;
256         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
257     }
258 
259     /**
260      * Creates a transition animation to the launcher activity.
261      *
262      * Warning: the resulting animation must be played, since this method has side effects on this
263      * controller's state.
264      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)265     public Animator createAnimToLauncher(@NonNull LauncherState toState,
266             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
267         // If going to overview, stash the task bar
268         // If going home, align the icons to hotseat
269         AnimatorSet animatorSet = new AnimatorSet();
270 
271         // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
272         TaskbarStashController stashController = mControllers.taskbarStashController;
273         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
274                 toState.isTaskbarStashed(mLauncher));
275         if (DEBUG) {
276             Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false);
277         }
278         stashController.updateStateForFlag(FLAG_IN_APP, false);
279 
280         updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true);
281         animatorSet.play(stashController.createApplyStateAnimator(duration));
282         animatorSet.play(applyState(duration, false));
283 
284         if (mTaskBarRecentsAnimationListener != null) {
285             mTaskBarRecentsAnimationListener.endGestureStateOverride(
286                     !mLauncher.isInState(LauncherState.OVERVIEW));
287         }
288         mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
289         callbacks.addListener(mTaskBarRecentsAnimationListener);
290         ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
291                 mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
292         return animatorSet;
293     }
294 
isAnimatingToLauncher()295     public boolean isAnimatingToLauncher() {
296         return mIsAnimatingToLauncher;
297     }
298 
setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)299     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
300         if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) {
301             // Animate the animation we have delayed immediately. This is usually triggered when
302             // the user has released their finger.
303             applyState();
304         }
305         mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim;
306     }
307 
308     /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
updateStateForSysuiFlags(int systemUiStateFlags)309     public void updateStateForSysuiFlags(int systemUiStateFlags) {
310         updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
311     }
312 
updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState)313     private  void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
314         final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
315         final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
316 
317         updateStateForFlag(FLAG_AWAKE, currIsAwake);
318         if (prevIsAwake != currIsAwake) {
319             // The screen is switching between on/off. When turning off, capture whether the
320             // launcher is active and memoize this state.
321             updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
322                     prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE));
323         }
324 
325         boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
326         updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
327 
328         // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
329         // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
330         // when the device is asleep, the second condition extends ensures that the transition from
331         // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
332         // hide/reveal animation timings.
333         boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
334                 || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
335         updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
336 
337         if (applyState) {
338             applyState();
339         }
340     }
341 
342     /**
343      * Updates the proper flag to change the state of the task bar.
344      *
345      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
346      *
347      * @param flag    The flag to update.
348      * @param enabled Whether to enable the flag
349      */
updateStateForFlag(int flag, boolean enabled)350     public void updateStateForFlag(int flag, boolean enabled) {
351         if (enabled) {
352             mState |= flag;
353         } else {
354             mState &= ~flag;
355         }
356     }
357 
hasAnyFlag(int flagMask)358     private boolean hasAnyFlag(int flagMask) {
359         return hasAnyFlag(mState, flagMask);
360     }
361 
hasAnyFlag(int flags, int flagMask)362     private boolean hasAnyFlag(int flags, int flagMask) {
363         return (flags & flagMask) != 0;
364     }
365 
applyState()366     public void applyState() {
367         applyState(mControllers.taskbarStashController.getStashDuration());
368     }
369 
applyState(long duration)370     public void applyState(long duration) {
371         applyState(duration, true);
372     }
373 
applyState(long duration, boolean start)374     public Animator applyState(long duration, boolean start) {
375         if (mControllers.taskbarActivityContext.isDestroyed()) {
376             return null;
377         }
378         Animator animator = null;
379         if (mPrevState == null || mPrevState != mState) {
380             // If this is our initial state, treat all flags as changed.
381             int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
382 
383             if (DEBUG) {
384                 String stateString;
385                 if (mPrevState == null) {
386                     stateString = getStateString(mState) + "(initial update)";
387                 } else {
388                     stateString = formatFlagChange(mState, mPrevState,
389                             TaskbarLauncherStateController::getStateString);
390                 }
391                 Log.d(TAG, "applyState: " + stateString
392                         + ", duration: " + duration
393                         + ", start: " + start);
394             }
395             mPrevState = mState;
396             animator = onStateChangeApplied(changedFlags, duration, start);
397         }
398         return animator;
399     }
400 
onStateChangeApplied(int changedFlags, long duration, boolean start)401     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
402         final boolean isInLauncher = isInLauncher();
403         final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
404         final float toAlignment = isIconAlignedWithHotseat ? 1 : 0;
405         boolean handleOpenFloatingViews = false;
406         if (DEBUG) {
407             Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher
408                     + ", mLauncherState: " + mLauncherState
409                     + ", toAlignment: " + toAlignment);
410         }
411         mControllers.bubbleControllers.ifPresent(controllers -> {
412             // Show the bubble bar when on launcher home or in overview.
413             boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL;
414             boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
415             controllers.bubbleStashController.setBubblesShowingOnHome(onHome);
416             controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
417         });
418 
419         AnimatorSet animatorSet = new AnimatorSet();
420 
421         if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
422             boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION);
423             playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted);
424 
425             if (launcherTransitionCompleted
426                     && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) {
427                 // We're about to be paused, set immediately to ensure seamless handoff.
428                 updateStateForFlag(FLAG_RESUMED, false);
429                 applyState(0 /* duration */);
430             }
431             if (mLauncherState == LauncherState.NORMAL) {
432                 // We're changing state to home, should close open popups e.g. Taskbar AllApps
433                 handleOpenFloatingViews = true;
434             }
435             if (mLauncherState == LauncherState.OVERVIEW) {
436                 // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
437                 mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
438             }
439         }
440 
441         if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) {
442             animatorSet.addListener(new AnimatorListenerAdapter() {
443                 @Override
444                 public void onAnimationStart(Animator animation) {
445                     mIsAnimatingToLauncher = isInLauncher;
446 
447                     TaskbarStashController stashController =
448                             mControllers.taskbarStashController;
449                     if (DEBUG) {
450                         Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
451                     }
452                     stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher);
453                     stashController.applyState(duration);
454                 }
455 
456                 @Override
457                 public void onAnimationEnd(Animator animation) {
458                     mIsAnimatingToLauncher = false;
459                 }
460             });
461 
462             // Handle closing open popups when going home/overview
463             handleOpenFloatingViews = true;
464         }
465 
466         if (handleOpenFloatingViews && isInLauncher) {
467             AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
468         }
469 
470         if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
471             // Take note of the current time, as the taskbar is made visible again.
472             mLastUnlockTimeMs = SystemClock.elapsedRealtime();
473         }
474 
475         boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
476         float taskbarAlpha = isHidden ? 0 : 1;
477         if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) {
478             Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha);
479 
480             taskbarVisibility.setDuration(duration);
481             if (isHidden) {
482                 // Stash the transient taskbar once the taskbar is not visible. This reduces
483                 // visual noise when unlocking the device afterwards.
484                 animatorSet.addListener(new AnimatorListenerAdapter() {
485                     @Override
486                     public void onAnimationEnd(Animator animation) {
487                         TaskbarStashController stashController =
488                                 mControllers.taskbarStashController;
489                         stashController.updateAndAnimateTransientTaskbar(
490                                 /* stash */ true, /* bubblesShouldFollow */ true);
491                     }
492                 });
493             } else {
494                 // delay the fade in animation a bit to reduce visual noise when waking up a device
495                 // with a fingerprint reader. This should only be done when the device was woken
496                 // up via fingerprint reader, however since this information is currently not
497                 // available, opting to always delay the fade-in a bit.
498                 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
499                 taskbarVisibility.setStartDelay(
500                         Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
501             }
502             animatorSet.play(taskbarVisibility);
503         }
504 
505         float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
506 
507         // Don't animate if background has reached desired value.
508         if (mTaskbarBackgroundAlpha.isAnimating()
509                 || mTaskbarBackgroundAlpha.value != backgroundAlpha) {
510             mTaskbarBackgroundAlpha.cancelAnimation();
511             if (DEBUG) {
512                 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
513                         + mTaskbarBackgroundAlpha.value
514                         + " -> " + backgroundAlpha + ": " + duration);
515             }
516 
517             boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat;
518             boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat;
519             boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
520 
521             float startDelay = 0;
522             // We want to delay the background from fading in so that the icons have time to move
523             // into the bounds of the background before it appears.
524             if (isInLauncherIconNotAligned) {
525                 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
526             } else if (notInLauncherIconNotAligned) {
527                 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
528             }
529             float newDuration = duration - startDelay;
530             if (isInLauncherIconIsAligned) {
531                 // Make the background fade out faster so that it is gone by the time the
532                 // icons move outside of the bounds of the background.
533                 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;
534             }
535             Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
536                     .animateToValue(backgroundAlpha)
537                     .setDuration((long) newDuration);
538             taskbarBackgroundAlpha.setStartDelay((long) startDelay);
539             animatorSet.play(taskbarBackgroundAlpha);
540         }
541 
542         float cornerRoundness = isInLauncher ? 0 : 1;
543 
544         // Don't animate if corner roundness has reached desired value.
545         if (mTaskbarCornerRoundness.isAnimating()
546                 || mTaskbarCornerRoundness.value != cornerRoundness) {
547             mTaskbarCornerRoundness.cancelAnimation();
548             if (DEBUG) {
549                 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - "
550                         + mTaskbarCornerRoundness.value
551                         + " -> " + cornerRoundness + ": " + duration);
552             }
553             animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness));
554         }
555 
556         // Keep isUnlockTransition in sync with its counterpart in
557         // TaskbarStashController#createAnimToIsStashed.
558         boolean isUnlockTransition =
559                 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
560         if (isUnlockTransition) {
561             // When transitioning to unlocked, ensure the hotseat is fully visible from the
562             // beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
563             mIconAlignment.cancelAnimation();
564             // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
565             // change in value
566             mIconAlignment.updateValue(toAlignment);
567 
568             // Make sure FLAG_IN_APP is set when launching applications from keyguard.
569             if (!isInLauncher) {
570                 mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
571                 mControllers.taskbarStashController.applyState(0);
572             }
573         } else if (mIconAlignment.isAnimatingToValue(toAlignment)
574                 || mIconAlignment.isSettledOnValue(toAlignment)) {
575             // Already at desired value, but make sure we run the callback at the end.
576             animatorSet.addListener(AnimatorListeners.forEndCallback(
577                     this::onIconAlignmentRatioChanged));
578         } else {
579             mIconAlignment.cancelAnimation();
580             ObjectAnimator iconAlignAnim = mIconAlignment
581                     .animateToValue(toAlignment)
582                     .setDuration(duration);
583             if (DEBUG) {
584                 Log.d(TAG, "onStateChangeApplied - iconAlignment - "
585                         + mIconAlignment.value
586                         + " -> " + toAlignment + ": " + duration);
587             }
588             animatorSet.play(iconAlignAnim);
589         }
590 
591         animatorSet.setInterpolator(EMPHASIZED);
592 
593         if (start) {
594             animatorSet.start();
595         }
596         return animatorSet;
597     }
598 
599     /**
600      * Whether the taskbar is aligned with the hotseat in the current/target launcher state.
601      *
602      * This refers to the intended state - a transition to this state might be in progress.
603      */
isTaskbarAlignedWithHotseat()604     public boolean isTaskbarAlignedWithHotseat() {
605         return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
606     }
607 
608     /**
609      * Returns if icons should be aligned to hotseat in the current transition
610      */
isIconAlignedWithHotseat()611     public boolean isIconAlignedWithHotseat() {
612         if (isInLauncher()) {
613             boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
614             boolean willStashVisually = isInStashedState
615                     && mControllers.taskbarStashController.supportsVisualStashing();
616             boolean isTaskbarAlignedWithHotseat =
617                     mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
618             return isTaskbarAlignedWithHotseat && !willStashVisually;
619         } else {
620             return false;
621         }
622     }
623 
624     /**
625      * Returns if the current Launcher state has hotseat on top of other elemnets.
626      */
isInHotseatOnTopStates()627     public boolean isInHotseatOnTopStates() {
628         return mLauncherState != LauncherState.ALL_APPS;
629     }
630 
isInOverview()631     boolean isInOverview() {
632         return mLauncherState == LauncherState.OVERVIEW;
633     }
634 
playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)635     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
636             boolean committed) {
637         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
638         TaskbarStashController stashController = mControllers.taskbarStashController;
639         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
640         Animator stashAnimator = stashController.createApplyStateAnimator(duration);
641         if (stashAnimator != null) {
642             stashAnimator.addListener(new AnimatorListenerAdapter() {
643                 @Override
644                 public void onAnimationEnd(Animator animation) {
645                     if (isInStashedState && committed) {
646                         // Reset hotseat alpha to default
647                         mLauncher.getHotseat().setIconsAlpha(1);
648                     }
649                 }
650 
651                 @Override
652                 public void onAnimationStart(Animator animation) {
653                     if (mLauncher.getHotseat().getIconsAlpha() > 0) {
654                         updateIconAlphaForHome(mLauncher.getHotseat().getIconsAlpha());
655                     }
656                 }
657             });
658             animatorSet.play(stashAnimator);
659         }
660 
661         // Translate back to 0 at a shorter or same duration as the icon alignment animation.
662         // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
663         // overview to home. When not in app, we do duration / 2 just to make it feel snappier.
664         long resetDuration = mControllers.taskbarStashController.isInApp()
665                 ? duration
666                 : duration / 2;
667         if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
668                 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
669             animatorSet.play(mControllers.taskbarTranslationController
670                     .createAnimToResetTranslation(resetDuration));
671         }
672     }
673 
674     /** Whether the launcher is considered active. */
isInLauncher()675     private boolean isInLauncher() {
676         if (hasAnyFlag(FLAG_AWAKE)) {
677             return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE);
678         } else {
679             return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE);
680         }
681     }
682 
683     /**
684      * Resets and updates the icon alignment.
685      */
resetIconAlignment()686     protected void resetIconAlignment() {
687         mIconAlignment.finishAnimation();
688         onIconAlignmentRatioChanged();
689     }
690 
onIconAlignmentRatioChanged()691     private void onIconAlignmentRatioChanged() {
692         float currentValue = mIconAlphaForHome.getValue();
693         boolean taskbarWillBeVisible = mIconAlignment.value < 1;
694         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
695                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
696 
697         mControllers.taskbarViewController.setLauncherIconAlignment(
698                 mIconAlignment.value, mLauncher.getDeviceProfile());
699         mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
700         // Switch taskbar and hotseat in last frame
701         updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
702 
703         // Sync the first frame where we swap taskbar and hotseat.
704         if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
705             ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
706                     mControllers.taskbarActivityContext.getDragLayer(),
707                     () -> {
708                     });
709         }
710     }
711 
updateIconAlphaForHome(float alpha)712     private void updateIconAlphaForHome(float alpha) {
713         if (mControllers.taskbarActivityContext.isDestroyed()) {
714             return;
715         }
716         mIconAlphaForHome.setValue(alpha);
717         boolean hotseatVisible = alpha == 0
718                 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
719                 && mIconAlignment.value > 0);
720         /*
721          * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
722          * should not be visible at the same time.
723          */
724         mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
725         if (mIsQsbInline) {
726             mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
727         }
728     }
729 
730     private final class TaskBarRecentsAnimationListener implements
731             RecentsAnimationCallbacks.RecentsAnimationListener {
732         private final RecentsAnimationCallbacks mCallbacks;
733 
TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks)734         TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
735             mCallbacks = callbacks;
736         }
737 
738         @Override
onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)739         public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
740             boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW);
741             endGestureStateOverride(!isInOverview);
742         }
743 
744         @Override
onRecentsAnimationFinished(RecentsAnimationController controller)745         public void onRecentsAnimationFinished(RecentsAnimationController controller) {
746             endGestureStateOverride(!controller.getFinishTargetIsLauncher());
747         }
748 
endGestureStateOverride(boolean finishedToApp)749         private void endGestureStateOverride(boolean finishedToApp) {
750             mCallbacks.removeListener(this);
751             mTaskBarRecentsAnimationListener = null;
752             ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
753 
754             // Update the resumed state immediately to ensure a seamless handoff
755             boolean launcherResumed = !finishedToApp;
756             updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false);
757             updateStateForFlag(FLAG_RESUMED, launcherResumed);
758             applyState();
759 
760             TaskbarStashController controller = mControllers.taskbarStashController;
761             if (DEBUG) {
762                 Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
763             }
764             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
765             controller.applyState();
766         }
767     }
768 
getStateString(int flags)769     private static String getStateString(int flags) {
770         StringJoiner result = new StringJoiner("|");
771         appendFlag(result, flags, FLAG_RESUMED, "resumed");
772         appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed");
773         appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION,
774                 "launcher_in_state_transition");
775         appendFlag(result, flags, FLAG_AWAKE, "awake");
776         appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
777                 "was_active_while_awake");
778         appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked");
779         return result.toString();
780     }
781 
dumpLogs(String prefix, PrintWriter pw)782     protected void dumpLogs(String prefix, PrintWriter pw) {
783         pw.println(prefix + "TaskbarLauncherStateController:");
784         pw.println(String.format(
785                 "%s\tmIconAlignment=%.2f",
786                 prefix,
787                 mIconAlignment.value));
788         pw.println(String.format(
789                 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
790         pw.println(String.format(
791                 "%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue()));
792         pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState)));
793         pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
794         pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
795         pw.println(String.format(
796                 "%s\tmIsAnimatingToLauncher=%b",
797                 prefix,
798                 mIsAnimatingToLauncher));
799         pw.println(String.format(
800                 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
801     }
802 }
803