• 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.app.animation.Interpolators.FINAL_FRAME;
20 import static com.android.app.animation.Interpolators.INSTANT;
21 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
22 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT;
23 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH;
24 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
25 import static com.android.launcher3.Utilities.isRtl;
26 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
27 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
28 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
29 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_FOR_BUBBLES;
30 import static com.android.launcher3.taskbar.TaskbarStashController.UNLOCK_TRANSITION_MEMOIZATION_MS;
31 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
32 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
33 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
34 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
35 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
36 import static com.android.quickstep.fallback.RecentsStateUtilsKt.toLauncherState;
37 import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
38 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
39 
40 import android.animation.Animator;
41 import android.animation.AnimatorListenerAdapter;
42 import android.animation.AnimatorSet;
43 import android.animation.ObjectAnimator;
44 import android.os.SystemClock;
45 import android.util.Log;
46 import android.view.animation.Interpolator;
47 
48 import androidx.annotation.NonNull;
49 import androidx.annotation.Nullable;
50 
51 import com.android.app.animation.Interpolators;
52 import com.android.launcher3.AbstractFloatingView;
53 import com.android.launcher3.DeviceProfile;
54 import com.android.launcher3.Hotseat;
55 import com.android.launcher3.Hotseat.HotseatQsbAlphaId;
56 import com.android.launcher3.LauncherState;
57 import com.android.launcher3.QuickstepTransitionManager;
58 import com.android.launcher3.Utilities;
59 import com.android.launcher3.anim.AnimatedFloat;
60 import com.android.launcher3.anim.AnimatorListeners;
61 import com.android.launcher3.statemanager.StateManager;
62 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState;
63 import com.android.launcher3.uioverrides.QuickstepLauncher;
64 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
65 import com.android.quickstep.RecentsAnimationCallbacks;
66 import com.android.quickstep.RecentsAnimationController;
67 import com.android.quickstep.fallback.RecentsState;
68 import com.android.quickstep.fallback.window.RecentsDisplayModel;
69 import com.android.quickstep.fallback.window.RecentsWindowFlags;
70 import com.android.quickstep.fallback.window.RecentsWindowManager;
71 import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
72 import com.android.quickstep.util.SystemUiFlagUtils;
73 import com.android.quickstep.views.RecentsView;
74 import com.android.systemui.animation.ViewRootSync;
75 import com.android.systemui.shared.recents.model.ThumbnailData;
76 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
77 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
78 
79 import java.io.PrintWriter;
80 import java.util.HashMap;
81 import java.util.StringJoiner;
82 import java.util.function.Consumer;
83 
84 /**
85  * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
86  * the task bar accordingly.
87  */
88 public class TaskbarLauncherStateController {
89 
90     private static final String TAG = "TaskbarLauncherStateController";
91     private static final boolean DEBUG = false;
92 
93     /** Launcher activity is visible and focused. */
94     public static final int FLAG_VISIBLE = 1 << 0;
95 
96     /**
97      * A external transition / animation is running that will result in FLAG_VISIBLE being set.
98      **/
99     public static final int FLAG_TRANSITION_TO_VISIBLE = 1 << 1;
100 
101     /**
102      * Set while the launcher state machine is performing a state transition, see {@link
103      * StateManager.StateListener}.
104      */
105     public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2;
106 
107     /**
108      * Whether the screen is currently on, or is transitioning to be on.
109      *
110      * This is cleared as soon as the screen begins to transition off.
111      */
112     private static final int FLAG_AWAKE = 1 << 3;
113 
114     /**
115      * Captures whether the launcher was active at the time the FLAG_AWAKE was cleared.
116      * Always cleared when FLAG_AWAKE is set.
117      * <p>
118      * FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this
119      * point. Thus, this flag indicates whether the launcher will be shown when the device wakes up
120      * again.
121      */
122     private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4;
123 
124     /**
125      * Whether the device is currently locked.
126      * <ul>
127      *  <li>While locked, the taskbar is always stashed.<li/>
128      *  <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li>
129      * </ul>
130      */
131     private static final int FLAG_DEVICE_LOCKED = 1 << 5;
132 
133     /**
134      * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed).
135      * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by
136      * hiding the inset.
137      *
138      * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible
139      * resting state while hidden is stashed.
140      */
141     private static final int FLAG_TASKBAR_HIDDEN = 1 << 6;
142 
143     private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_VISIBLE | FLAG_TRANSITION_TO_VISIBLE;
144     /** Equivalent to an int with all 1s for binary operation purposes */
145     private static final int FLAGS_ALL = ~0;
146 
147     private static final float TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f;
148     private static final float TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f;
149     private static final float TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT = 0.25f;
150 
151     /**
152      * Delay for the taskbar fade-in.
153      *
154      * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions
155      * to launcher directly. The delay avoids the navbar to become briefly visible. The duration
156      * is the same as in SysUI, see http://shortn/_uNSbDoRUSr.
157      */
158     private static final long TASKBAR_SHOW_DELAY_MS = 250;
159 
160     private final AnimatedFloat mIconAlignment =
161             new AnimatedFloat(this::onIconAlignmentRatioChanged);
162 
163     private TaskbarControllers mControllers;
164     private AnimatedFloat mTaskbarBackgroundAlpha;
165     private AnimatedFloat mTaskbarAlpha;
166     private AnimatedFloat mTaskbarCornerRoundness;
167     private MultiProperty mTaskbarAlphaForHome;
168     private @Nullable Animator mHotseatTranslationXAnimation;
169     private QuickstepLauncher mLauncher;
170 
171     private boolean mIsDestroyed = false;
172     private Integer mPrevState;
173     private int mState;
174     private LauncherState mLauncherState = LauncherState.NORMAL;
175     private boolean mSkipNextRecentsAnimEnd;
176 
177     // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
178     private long mLastRemoveTaskbarHiddenTimeMs = 0;
179     /**
180      * Time when FLAG_DEVICE_LOCKED was last cleared, plus
181      * {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS}
182      */
183     private long mLastUnlockTransitionTimeout;
184 
185     private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
186 
187     private boolean mIsAnimatingToLauncher;
188 
189     private boolean mShouldDelayLauncherStateAnim;
190 
191     private @Nullable BubbleBarLocation mBubbleBarLocation;
192 
193     // We skip any view synchronizations during init/destroy.
194     private boolean mCanSyncViews;
195 
196     private boolean mIsQsbInline;
197 
198     private RecentsAnimationCallbacks mRecentsAnimationCallbacks;
199 
200     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
201             new DeviceProfile.OnDeviceProfileChangeListener() {
202                 @Override
203                 public void onDeviceProfileChanged(DeviceProfile dp) {
204                     if (mIsQsbInline && !dp.isQsbInline) {
205                         // We only modify QSB alpha if isQsbInline = true. If we switch to a DP
206                         // where isQsbInline = false, then we need to reset the alpha.
207                         mLauncher.getHotseat().setQsbAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
208                     }
209                     mIsQsbInline = dp.isQsbInline;
210                     TaskbarLauncherStateController.this.updateIconAlphaForHome(
211                             mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
212                     TaskbarLauncherStateController.this.onBubbleBarLocationChanged(
213                             mBubbleBarLocation, /* animate = */ false);
214                 }
215             };
216 
217     private final StateManager.StateListener<LauncherState> mStateListener =
218             new StateManager.StateListener<>() {
219 
220                 @Override
221                 public void onStateTransitionStart(LauncherState toState) {
222                     if (toState != mLauncherState) {
223                         // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a
224                         // previous state transition was already running, so we update the new
225                         // target.
226                         mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION;
227                         mLauncherState = toState;
228                     }
229                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
230                     if (!mShouldDelayLauncherStateAnim) {
231                         if (toState == LauncherState.NORMAL) {
232                             TaskbarActivityContext activity = mControllers.taskbarActivityContext;
233                             boolean isPinnedTaskbarAndNotInDesktopMode =
234                                     !activity.isInDesktopMode() && activity.isPinnedTaskbar();
235                             applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
236                                     isPinnedTaskbarAndNotInDesktopMode));
237                         } else {
238                             applyState();
239                         }
240                     }
241                 }
242 
243                 @Override
244                 public void onStateTransitionComplete(LauncherState finalState) {
245                     mLauncherState = finalState;
246                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false);
247                     applyState();
248                     updateOverviewDragState(finalState);
249                 }
250             };
251 
252     private final StateManager.StateListener<RecentsState> mRecentsStateListener =
253             new StateManager.StateListener<>() {
254 
255                 @Override
256                 public void onStateTransitionStart(RecentsState toState) {
257                     mStateListener.onStateTransitionStart(toLauncherState(toState));
258                 }
259 
260                 @Override
261                 public void onStateTransitionComplete(RecentsState finalState) {
262                     mStateListener.onStateTransitionComplete(toLauncherState(finalState));
263                 }
264             };
265 
266     /**
267      * Callback for when launcher state transition completes after user swipes to home.
268      * @param finalState The final state of the transition.
269      */
onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState)270     public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
271         // TODO(b/279514548) Cleans up bad state that can occur when user interacts with
272         // taskbar on top of transparent activity.
273         if ((finalState == LauncherState.NORMAL)
274                 && mLauncher.hasBeenResumed()) {
275             updateStateForFlag(FLAG_VISIBLE, true);
276             applyState();
277         }
278     }
279 
280     /** Initializes the controller instance, and applies the initial state immediately. */
init(TaskbarControllers controllers, QuickstepLauncher launcher, @SystemUiStateFlags long sysuiStateFlags)281     public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
282             @SystemUiStateFlags long sysuiStateFlags) {
283         mCanSyncViews = false;
284 
285         mControllers = controllers;
286         mLauncher = launcher;
287 
288         mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline;
289 
290         mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
291                 .getTaskbarBackgroundAlpha();
292         mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha();
293         mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness();
294         mTaskbarAlphaForHome = mControllers.taskbarViewController
295                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
296 
297         resetIconAlignment();
298 
299         if (!mControllers.taskbarActivityContext.isPhoneMode()) {
300             mLauncher.getStateManager().addStateListener(mStateListener);
301             runForRecentsWindowManager(recentsWindowManager ->
302                     recentsWindowManager.getStateManager().addStateListener(mRecentsStateListener));
303         }
304         mLauncherState = launcher.getStateManager().getState();
305         updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
306 
307         applyState(0);
308 
309         mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode();
310         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
311         updateOverviewDragState(mLauncherState);
312     }
313 
onDestroy()314     public void onDestroy() {
315         mIsDestroyed = true;
316         mCanSyncViews = false;
317 
318         if (mRecentsAnimationCallbacks != null) {
319             mRecentsAnimationCallbacks.removeListener(mTaskBarRecentsAnimationListener);
320             mRecentsAnimationCallbacks = null;
321         }
322 
323         mIconAlignment.finishAnimation();
324 
325         mLauncher.getHotseat().setIconsAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
326         mLauncher.getStateManager().removeStateListener(mStateListener);
327         runForRecentsWindowManager(recentsWindowManager ->
328                 recentsWindowManager.getStateManager().removeStateListener(mRecentsStateListener));
329 
330         mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode();
331         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
332     }
333 
334     /**
335      * Creates a transition animation to the launcher activity.
336      *
337      * Warning: the resulting animation must be played, since this method has side effects on this
338      * controller's state.
339      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)340     public Animator createAnimToLauncher(@NonNull LauncherState toState,
341             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
342         // If going to overview, stash the task bar
343         // If going home, align the icons to hotseat
344         AnimatorSet animatorSet = new AnimatorSet();
345         mRecentsAnimationCallbacks = callbacks;
346 
347         // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
348         TaskbarStashController stashController = mControllers.taskbarStashController;
349         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
350                 toState.isTaskbarStashed(mLauncher));
351         if (DEBUG) {
352             Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false);
353         }
354         stashController.updateStateForFlag(FLAG_IN_APP, false);
355 
356         updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
357         mLauncherState = toState;
358         animatorSet.play(stashController.createApplyStateAnimator(duration));
359         animatorSet.play(applyState(duration, false));
360 
361         if (mTaskBarRecentsAnimationListener != null) {
362             mTaskBarRecentsAnimationListener.endGestureStateOverride(
363                     !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/);
364         }
365         mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
366         callbacks.addListener(mTaskBarRecentsAnimationListener);
367         RecentsView recentsView = mControllers.uiController.getRecentsView();
368         if (recentsView != null) {
369             recentsView.setTaskLaunchListener(() -> mTaskBarRecentsAnimationListener
370                     .endGestureStateOverride(true, false /*canceled*/));
371             recentsView.setTaskLaunchCancelledRunnable(() -> {
372                 updateStateForUserFinishedToApp(false /* finishedToApp */);
373             });
374         }
375 
376         return animatorSet;
377     }
378 
isAnimatingToLauncher()379     public boolean isAnimatingToLauncher() {
380         return mIsAnimatingToLauncher;
381     }
382 
setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)383     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
384         if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) {
385             // Animate the animation we have delayed immediately. This is usually triggered when
386             // the user has released their finger.
387             applyState();
388         }
389         mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim;
390     }
391 
392     /** Will make the next onRecentsAnimationFinished() a no-op. */
setSkipNextRecentsAnimEnd()393     public void setSkipNextRecentsAnimEnd() {
394         mSkipNextRecentsAnimEnd = true;
395     }
396 
397     /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
updateStateForSysuiFlags(@ystemUiStateFlags long systemUiStateFlags)398     public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) {
399         updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
400     }
401 
updateStateForSysuiFlags(@ystemUiStateFlags long systemUiStateFlags, boolean applyState)402     private void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags,
403             boolean applyState) {
404         final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
405         final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
406 
407         updateStateForFlag(FLAG_AWAKE, currIsAwake);
408         if (prevIsAwake != currIsAwake) {
409             // The screen is switching between on/off. When turning off, capture whether the
410             // launcher is active and memoize this state.
411             updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
412                     prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE));
413         }
414 
415         updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags));
416 
417         updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden(systemUiStateFlags));
418 
419         if (applyState) {
420             applyState();
421         }
422     }
423 
424     /**
425      * Updates overview drag state on various controllers based on {@link #mLauncherState}.
426      *
427      * @param launcherState The current state launcher is in
428      */
updateOverviewDragState(LauncherState launcherState)429     private void updateOverviewDragState(LauncherState launcherState) {
430         boolean disallowLongClick = mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher;
431         com.android.launcher3.taskbar.Utilities.setOverviewDragState(
432                 mControllers, launcherState.disallowTaskbarGlobalDrag(),
433                 disallowLongClick, launcherState.allowTaskbarInitialSplitSelection());
434     }
435 
436     /**
437      * Updates the proper flag to change the state of the task bar.
438      *
439      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
440      *
441      * @param flag    The flag to update.
442      * @param enabled Whether to enable the flag
443      */
updateStateForFlag(int flag, boolean enabled)444     public void updateStateForFlag(int flag, boolean enabled) {
445         if (enabled) {
446             mState |= flag;
447         } else {
448             mState &= ~flag;
449         }
450     }
451 
hasAnyFlag(long flagMask)452     private boolean hasAnyFlag(long flagMask) {
453         return hasAnyFlag(mState, flagMask);
454     }
455 
hasAnyFlag(long flags, long flagMask)456     private boolean hasAnyFlag(long flags, long flagMask) {
457         return (flags & flagMask) != 0;
458     }
459 
applyState()460     public void applyState() {
461         applyState(mControllers.taskbarStashController.getStashDuration());
462     }
463 
applyState(long duration)464     public void applyState(long duration) {
465         applyState(duration, true);
466     }
467 
applyState(long duration, boolean start)468     public Animator applyState(long duration, boolean start) {
469         if (mIsDestroyed || mControllers.taskbarActivityContext.isPhoneMode()) {
470             return null;
471         }
472         Animator animator = null;
473         if (mPrevState == null || mPrevState != mState) {
474             // If this is our initial state, treat all flags as changed.
475             int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
476 
477             if (DEBUG) {
478                 String stateString;
479                 if (mPrevState == null) {
480                     stateString = getStateString(mState) + "(initial update)";
481                 } else {
482                     stateString = formatFlagChange(mState, mPrevState,
483                             TaskbarLauncherStateController::getStateString);
484                 }
485                 Log.d(TAG, "applyState: " + stateString
486                         + ", duration: " + duration
487                         + ", start: " + start);
488             }
489             mPrevState = mState;
490             animator = onStateChangeApplied(changedFlags, duration, start);
491         }
492         return animator;
493     }
494 
onStateChangeApplied(int changedFlags, long duration, boolean start)495     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
496         final boolean isInLauncher = isInLauncher();
497         final boolean isInOverview = mControllers.uiController.isInOverviewUi();
498         final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
499         final float toAlignment = isIconAlignedWithHotseat ? 1 : 0;
500         boolean handleOpenFloatingViews = false;
501         boolean isPinnedTaskbar =
502                 mControllers.taskbarActivityContext.isPinnedTaskbar();
503         if (DEBUG) {
504             Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher
505                     + ", mLauncherState: " + mLauncherState
506                     + ", toAlignment: " + toAlignment);
507         }
508         mControllers.bubbleControllers.ifPresent(controllers -> {
509             // Show the bubble bar when on launcher home (hotseat icons visible) or in overview
510             boolean onOverview = isInLauncher && mLauncherState == LauncherState.OVERVIEW;
511             boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
512                     mLauncher, HOTSEAT_ICONS);
513             BubbleLauncherState state = onOverview
514                     ? BubbleLauncherState.OVERVIEW
515                     : hotseatIconsVisible
516                             ? BubbleLauncherState.HOME
517                             : BubbleLauncherState.IN_APP;
518             controllers.bubbleStashController.setLauncherState(state);
519         });
520 
521         TaskbarStashController stashController = mControllers.taskbarStashController;
522         stashController.updateStateForFlag(FLAG_IN_OVERVIEW,
523                 mLauncherState == LauncherState.OVERVIEW);
524 
525         AnimatorSet animatorSet = new AnimatorSet();
526 
527         if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
528             boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION);
529             playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted);
530 
531             if (launcherTransitionCompleted
532                     && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) {
533                 // We're about to be paused, set immediately to ensure seamless handoff.
534                 updateStateForFlag(FLAG_VISIBLE, false);
535                 applyState(0 /* duration */);
536             }
537             if (mLauncherState == LauncherState.NORMAL) {
538                 // We're changing state to home, should close open popups e.g. Taskbar AllApps
539                 handleOpenFloatingViews = true;
540             }
541             if (mLauncherState == LauncherState.OVERVIEW
542                     && !mControllers.taskbarActivityContext.isPhoneMode()) {
543                 // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
544                 mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
545             }
546         }
547 
548         if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) {
549             animatorSet.addListener(new AnimatorListenerAdapter() {
550                 @Override
551                 public void onAnimationStart(Animator animation) {
552                     mIsAnimatingToLauncher = isInLauncher;
553 
554                     if (DEBUG) {
555                         Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
556                     }
557                     stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher);
558                     stashController.applyState(duration);
559                 }
560 
561                 @Override
562                 public void onAnimationEnd(Animator animation) {
563                     mIsAnimatingToLauncher = false;
564                 }
565             });
566 
567             // Handle closing open popups when going home/overview
568             handleOpenFloatingViews = true;
569         } else {
570             stashController.applyState();
571         }
572 
573         if (handleOpenFloatingViews && isInLauncher) {
574             AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
575         }
576 
577         if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
578             // Take note of the current time, as the taskbar is made visible again.
579             mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime();
580         }
581 
582         boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
583         float taskbarAlpha = isHidden ? 0 : 1;
584         if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) {
585             Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha);
586 
587             taskbarVisibility.setDuration(duration);
588             if (isHidden) {
589                 // Stash the transient taskbar once the taskbar is not visible. This reduces
590                 // visual noise when unlocking the device afterwards.
591                 animatorSet.addListener(new AnimatorListenerAdapter() {
592                     @Override
593                     public void onAnimationEnd(Animator animation) {
594                         TaskbarStashController stashController =
595                                 mControllers.taskbarStashController;
596                         stashController.updateAndAnimateTransientTaskbar(
597                                 /* stash */ true, /* bubblesShouldFollow */ true);
598                     }
599                 });
600             } else {
601                 // delay the fade in animation a bit to reduce visual noise when waking up a device
602                 // with a fingerprint reader. This should only be done when the device was woken
603                 // up via fingerprint reader, however since this information is currently not
604                 // available, opting to always delay the fade-in a bit.
605                 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime()
606                         - mLastRemoveTaskbarHiddenTimeMs;
607                 taskbarVisibility.setStartDelay(
608                         Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
609             }
610             animatorSet.play(taskbarVisibility);
611         }
612 
613         float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
614         AnimatedFloat taskbarBgOffset =
615                 mControllers.taskbarDragLayerController.getTaskbarBackgroundOffset();
616         boolean showTaskbar = shouldShowTaskbar(mControllers.taskbarActivityContext, isInLauncher,
617                 isInOverview);
618         float taskbarBgOffsetEnd = showTaskbar ? 0f : 1f;
619         float taskbarBgOffsetStart = showTaskbar ? 1f : 0f;
620 
621         // Don't animate if background has reached desired value.
622         if (mTaskbarBackgroundAlpha.isAnimating()
623                 || mTaskbarBackgroundAlpha.value != backgroundAlpha
624                 || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)
625                 || taskbarBgOffset.value != taskbarBgOffsetEnd) {
626             mTaskbarBackgroundAlpha.cancelAnimation();
627             if (DEBUG) {
628                 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
629                         + mTaskbarBackgroundAlpha.value
630                         + " -> " + backgroundAlpha + ": " + duration);
631             }
632 
633             boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat;
634             boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat;
635             boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
636             // When Hotseat icons are not on top don't change duration or add start delay.
637             // This will keep the duration in sync for icon alignment and background fade in/out.
638             // For example, launching app from launcher all apps.
639             boolean isHotseatIconOnTopWhenAligned =
640                     mControllers.uiController.isHotseatIconOnTopWhenAligned();
641 
642             float startDelay = 0;
643             // We want to delay the background from fading in so that the icons have time to move
644             // into the bounds of the background before it appears.
645             if (isInLauncherIconNotAligned) {
646                 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
647             } else if (notInLauncherIconNotAligned && isHotseatIconOnTopWhenAligned) {
648                 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
649             }
650             float newDuration = duration - startDelay;
651             if (isInLauncherIconIsAligned && isHotseatIconOnTopWhenAligned) {
652                 // Make the background fade out faster so that it is gone by the time the
653                 // icons move outside of the bounds of the background.
654                 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;
655             }
656             Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha.animateToValue(
657                     backgroundAlpha);
658             if (isPinnedTaskbar) {
659                 setupPinnedTaskbarAnimation(animatorSet, showTaskbar, taskbarBgOffset,
660                         taskbarBgOffsetStart, taskbarBgOffsetEnd, duration, taskbarBackgroundAlpha);
661             } else {
662                 taskbarBackgroundAlpha.setDuration((long) newDuration);
663                 taskbarBackgroundAlpha.setStartDelay((long) startDelay);
664             }
665             animatorSet.play(taskbarBackgroundAlpha);
666         }
667 
668         float cornerRoundness = isInLauncher ? 0 : 1;
669 
670         if (mControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
671                 mControllers.taskbarActivityContext.getDisplayId())
672                 && mControllers.getSharedState() != null) {
673             cornerRoundness =
674                     mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness(
675                             mControllers.getSharedState().showCornerRadiusInDesktopMode);
676         }
677 
678         // Don't animate if corner roundness has reached desired value.
679         if (mTaskbarCornerRoundness.isAnimating()
680                 || mTaskbarCornerRoundness.value != cornerRoundness) {
681             mTaskbarCornerRoundness.cancelAnimation();
682             if (DEBUG) {
683                 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - "
684                         + mTaskbarCornerRoundness.value
685                         + " -> " + cornerRoundness + ": " + duration);
686             }
687             animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness));
688         }
689 
690         // Keep isUnlockTransition in sync with its counterpart in
691         // TaskbarStashController#createAnimToIsStashed.
692         boolean isUnlockTransition =
693                 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
694         if (isUnlockTransition) {
695             // the launcher might not be resumed at the time the device is considered
696             // unlocked (when the keyguard goes away), but possibly shortly afterwards.
697             // To play the unlock transition at the time the unstash animation actually happens,
698             // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
699             mLastUnlockTransitionTimeout =
700                     SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
701         }
702         boolean isInUnlockTimeout = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
703         if (isUnlockTransition || isInUnlockTimeout) {
704             // When transitioning to unlocked, ensure the hotseat is fully visible from the
705             // beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
706             mIconAlignment.cancelAnimation();
707             // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
708             // change in value
709             mIconAlignment.updateValue(toAlignment);
710 
711             // Make sure FLAG_IN_APP is set when launching applications from keyguard.
712             if (!isInLauncher) {
713                 mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
714                 mControllers.taskbarStashController.applyState(0);
715             }
716         } else if (mIconAlignment.isAnimatingToValue(toAlignment)
717                 || mIconAlignment.isSettledOnValue(toAlignment)) {
718             // Already at desired value, but make sure we run the callback at the end.
719             animatorSet.addListener(AnimatorListeners.forEndCallback(() -> {
720                 if (!mIconAlignment.isAnimating()) {
721                     onIconAlignmentRatioChanged();
722                 }
723             }));
724         } else {
725             mIconAlignment.cancelAnimation();
726             ObjectAnimator iconAlignAnim = mIconAlignment
727                     .animateToValue(toAlignment)
728                     .setDuration(duration);
729             if (DEBUG) {
730                 Log.d(TAG, "onStateChangeApplied - iconAlignment - "
731                         + mIconAlignment.value
732                         + " -> " + toAlignment + ": " + duration);
733             }
734             if (!isPinnedTaskbar) {
735                 if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
736                     iconAlignAnim.setInterpolator(FINAL_FRAME);
737                 } else {
738                     animatorSet.play(iconAlignAnim);
739                 }
740             }
741         }
742 
743         Interpolator interpolator = enableScalingRevealHomeAnimation() && !isPinnedTaskbar
744                 ? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED;
745 
746         animatorSet.setInterpolator(interpolator);
747 
748         if (start) {
749             animatorSet.start();
750         }
751         return animatorSet;
752     }
753 
shouldShowTaskbar(TaskbarActivityContext activityContext, boolean isInLauncher, boolean isInOverview)754     private static boolean shouldShowTaskbar(TaskbarActivityContext activityContext,
755             boolean isInLauncher, boolean isInOverview) {
756         if (activityContext.showDesktopTaskbarForFreeformDisplay()) {
757             return true;
758         }
759 
760         if (activityContext.showLockedTaskbarOnHome() && isInLauncher) {
761             return true;
762         }
763         return !isInLauncher || isInOverview;
764     }
765 
setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar, AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd, long duration, Animator taskbarBackgroundAlpha)766     private void setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar,
767             AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd,
768             long duration, Animator taskbarBackgroundAlpha) {
769         float targetAlpha = !showTaskbar ? 1 : 0;
770         mLauncher.getHotseat().setIconsAlpha(targetAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
771         if (mIsQsbInline) {
772             mLauncher.getHotseat().setQsbAlpha(targetAlpha,
773                     ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
774         }
775 
776         if ((taskbarBgOffset.value != taskbarBgOffsetEnd && !taskbarBgOffset.isAnimating())
777                 || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)) {
778             taskbarBgOffset.cancelAnimation();
779             Animator taskbarIconAlpha = mTaskbarAlphaForHome.animateToValue(
780                     showTaskbar ? 1f : 0f);
781             AnimatedFloat taskbarIconTranslationYForHome =
782                     mControllers.taskbarViewController.mTaskbarIconTranslationYForHome;
783             ObjectAnimator taskbarBackgroundOffset = taskbarBgOffset.animateToValue(
784                     taskbarBgOffsetStart,
785                     taskbarBgOffsetEnd);
786             ObjectAnimator taskbarIconsYTranslation = null;
787             float taskbarHeight =
788                     mControllers.taskbarActivityContext.getDeviceProfile().taskbarHeight;
789             if (showTaskbar) {
790                 taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(
791                         taskbarHeight, 0);
792             } else {
793                 taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(0,
794                         taskbarHeight);
795             }
796 
797             taskbarIconAlpha.setDuration(duration);
798             taskbarIconsYTranslation.setDuration(duration);
799             taskbarBackgroundOffset.setDuration(duration);
800 
801             animatorSet.play(taskbarIconAlpha);
802             animatorSet.play(taskbarIconsYTranslation);
803             animatorSet.play(taskbarBackgroundOffset);
804         }
805         taskbarBackgroundAlpha.setInterpolator(showTaskbar ? INSTANT : FINAL_FRAME);
806         taskbarBackgroundAlpha.setDuration(duration);
807     }
808 
809     /**
810      * Whether the taskbar is aligned with the hotseat in the current/target launcher state.
811      *
812      * This refers to the intended state - a transition to this state might be in progress.
813      */
isTaskbarAlignedWithHotseat()814     public boolean isTaskbarAlignedWithHotseat() {
815         if (mControllers.taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) {
816             return false;
817         }
818 
819         if (mControllers.taskbarActivityContext.showLockedTaskbarOnHome() && isInLauncher()) {
820             return false;
821         }
822 
823         return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
824     }
825 
826     /**
827      * Returns if icons should be aligned to hotseat in the current transition
828      */
isIconAlignedWithHotseat()829     public boolean isIconAlignedWithHotseat() {
830         if (isInLauncher()) {
831             boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
832             boolean willStashVisually = isInStashedState
833                     && mControllers.taskbarStashController.supportsVisualStashing();
834             boolean isTaskbarAlignedWithHotseat = isTaskbarAlignedWithHotseat();
835             return isTaskbarAlignedWithHotseat && !willStashVisually;
836         } else {
837             return false;
838         }
839     }
840 
841     /**
842      * Returns if the current Launcher state has hotseat on top of other elemnets.
843      */
isInHotseatOnTopStates()844     public boolean isInHotseatOnTopStates() {
845         return mLauncherState != LauncherState.ALL_APPS
846                 && !mLauncher.getWorkspace().isOverlayShown();
847     }
848 
isInOverviewUi()849     boolean isInOverviewUi() {
850         return mLauncherState.isRecentsViewVisible;
851     }
852 
853     /**
854      * Returns the current mLauncherState. Note that this could represent RecentsState as well, as
855      * we convert those to equivalent LauncherStates even if Launcher Activity is not actually in
856      * those states (for the case where the state is represented in a separate Window instead).
857      */
getLauncherState()858     public LauncherState getLauncherState() {
859         return mLauncherState;
860     }
861 
playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)862     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
863             boolean committed) {
864         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
865         TaskbarStashController stashController = mControllers.taskbarStashController;
866         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
867         Animator stashAnimator = stashController.createApplyStateAnimator(duration);
868         if (stashAnimator != null) {
869             stashAnimator.addListener(new AnimatorListenerAdapter() {
870                 @Override
871                 public void onAnimationEnd(Animator animation) {
872                     if (isInStashedState && committed) {
873                         // Reset hotseat alpha to default
874                         mLauncher.getHotseat().setIconsAlpha(1, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
875                     }
876                 }
877 
878                 @Override
879                 public void onAnimationStart(Animator animation) {
880                     float hotseatIconsAlpha = mLauncher.getHotseat()
881                             .getIconsAlpha(ALPHA_CHANNEL_TASKBAR_ALIGNMENT)
882                             .getValue();
883                     if (hotseatIconsAlpha > 0) {
884                         updateIconAlphaForHome(hotseatIconsAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
885                     }
886                 }
887             });
888             animatorSet.play(stashAnimator);
889         }
890 
891         // Translate back to 0 at a shorter or same duration as the icon alignment animation.
892         // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
893         // overview to home. When not in app, we do duration / 2 just to make it feel snappier.
894         long resetDuration = mControllers.taskbarStashController.isInApp()
895                 ? duration
896                 : duration / 2;
897         if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
898                 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
899             animatorSet.play(mControllers.taskbarTranslationController
900                     .createAnimToResetTranslation(resetDuration));
901         }
902     }
903 
904     /** Whether the launcher is considered active. */
isInLauncher()905     private boolean isInLauncher() {
906         if (hasAnyFlag(FLAG_AWAKE)) {
907             return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE);
908         } else {
909             return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE);
910         }
911     }
912 
stashHotseat(boolean stash)913     protected void stashHotseat(boolean stash) {
914         // align taskbar with the hotseat icons before performing any animation
915         mControllers.taskbarViewController.setLauncherIconAlignment(/* alignmentRatio = */ 1,
916                 mLauncher.getDeviceProfile());
917         TaskbarStashController stashController = mControllers.taskbarStashController;
918         stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, stash);
919         Runnable swapHotseatWithTaskbar = new Runnable() {
920             @Override
921             public void run() {
922                 updateIconAlphaForHome(stash ? 1 : 0, ALPHA_CHANNEL_TASKBAR_STASH);
923             }
924         };
925         if (stash) {
926             stashController.applyState();
927             // if we stashing the hotseat we need to immediately swap it with the animating taskbar
928             swapHotseatWithTaskbar.run();
929         } else {
930             // if we revert stashing make swap after taskbar animation is complete
931             stashController.applyState(/* postApplyAction = */ swapHotseatWithTaskbar);
932         }
933     }
934 
unStashHotseatInstantly()935     protected void unStashHotseatInstantly() {
936         TaskbarStashController stashController = mControllers.taskbarStashController;
937         stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, false);
938         stashController.applyState(/* duration = */ 0);
939         updateIconAlphaForHome(/* taskbarAlpha = */ 0,
940                 ALPHA_CHANNEL_TASKBAR_STASH, /* updateTaskbarAlpha = */ false);
941     }
942 
943     /**
944      * Resets and updates the icon alignment.
945      */
resetIconAlignment()946     protected void resetIconAlignment() {
947         mIconAlignment.finishAnimation();
948         onIconAlignmentRatioChanged();
949     }
950 
onIconAlignmentRatioChanged()951     private void onIconAlignmentRatioChanged() {
952         float currentValue = mTaskbarAlphaForHome.getValue();
953         boolean taskbarWillBeVisible = mIconAlignment.value < 1;
954         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
955                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
956 
957         mControllers.taskbarViewController.setLauncherIconAlignment(
958                 mIconAlignment.value, mLauncher.getDeviceProfile());
959         mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
960         // Switch taskbar and hotseat in last frame and if taskbar is not hidden for bubbles
961         boolean isHiddenForBubbles = mControllers.taskbarStashController.isHiddenForBubbles();
962         updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0, ALPHA_CHANNEL_TASKBAR_ALIGNMENT,
963                 /* updateTaskbarAlpha = */ !isHiddenForBubbles);
964 
965         // Sync the first frame where we swap taskbar and hotseat.
966         if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
967             ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
968                     mControllers.taskbarActivityContext.getDragLayer(),
969                     () -> {});
970         }
971     }
972 
973     private void updateIconAlphaForHome(float taskbarAlpha, @HotseatQsbAlphaId int alphaChannel) {
974         updateIconAlphaForHome(taskbarAlpha, alphaChannel, /* updateTaskbarAlpha = */ true);
975     }
976 
977     private void updateIconAlphaForHome(float taskbarAlpha,
978             @HotseatQsbAlphaId int alphaChannel,
979             boolean updateTaskbarAlpha) {
980         if (mIsDestroyed) {
981             return;
982         }
983         if (updateTaskbarAlpha) {
984             mTaskbarAlphaForHome.setValue(taskbarAlpha);
985         }
986         boolean hotseatVisible = taskbarAlpha == 0
987                 || mControllers.taskbarActivityContext.isPhoneMode()
988                 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
989                 && mIconAlignment.value > 0);
990         /*
991          * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
992          * should not be visible at the same time.
993          */
994         float targetAlpha = hotseatVisible ? 1 : 0;
995         mLauncher.getHotseat().setIconsAlpha(targetAlpha, alphaChannel);
996         if (mIsQsbInline) {
997             mLauncher.getHotseat().setQsbAlpha(targetAlpha, alphaChannel);
998         }
999     }
1000 
1001     /** Updates launcher home screen appearance accordingly to the bubble bar location. */
1002     public void onBubbleBarLocationChanged(@Nullable BubbleBarLocation location, boolean animate) {
1003         mBubbleBarLocation = location;
1004         if (location == null) {
1005             // bubble bar is not present, hence no location, resetting the hotseat
1006             updateHotseatAndQsbTranslationX(/* targetValue = */ 0, animate);
1007             mBubbleBarLocation = null;
1008             return;
1009         }
1010         DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
1011         if (!deviceProfile.shouldAdjustHotseatOnNavBarLocationUpdate(
1012                 mControllers.taskbarActivityContext)) {
1013             return;
1014         }
1015         boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
1016         int targetX = deviceProfile
1017                 .getHotseatTranslationXForNavBar(mLauncher, isBubblesOnLeft);
1018         updateHotseatAndQsbTranslationX(targetX, animate);
1019     }
1020 
1021     /** Used to translate hotseat and QSB to make room for bubbles. */
1022     private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) {
1023         // cancel existing animation
1024         if (mHotseatTranslationXAnimation != null) {
1025             mHotseatTranslationXAnimation.cancel();
1026             mHotseatTranslationXAnimation = null;
1027         }
1028         Hotseat hotseat = mLauncher.getHotseat();
1029         AnimatorSet translationXAnimation = new AnimatorSet();
1030         MultiProperty iconsTranslationX = mLauncher.getHotseat()
1031                 .getIconsTranslationX(Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT);
1032         if (animate) {
1033             translationXAnimation.playTogether(iconsTranslationX.animateToValue(targetValue));
1034         } else {
1035             iconsTranslationX.setValue(targetValue);
1036         }
1037         float qsbTargetX = 0;
1038         if (mIsQsbInline) {
1039             qsbTargetX = targetValue;
1040         }
1041         MultiProperty qsbTranslationX = hotseat.getQsbTranslationX();
1042         if (qsbTranslationX != null) {
1043             if (animate) {
1044                 translationXAnimation.playTogether(qsbTranslationX.animateToValue(qsbTargetX));
1045             } else {
1046                 qsbTranslationX.setValue(qsbTargetX);
1047             }
1048         }
1049         if (!animate) {
1050             return;
1051         }
1052         mHotseatTranslationXAnimation = translationXAnimation;
1053         translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
1054         translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
1055         translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
1056         translationXAnimation.start();
1057     }
1058 
1059     private final class TaskBarRecentsAnimationListener implements
1060             RecentsAnimationCallbacks.RecentsAnimationListener {
1061         private final RecentsAnimationCallbacks mCallbacks;
1062 
1063         TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
1064             mCallbacks = callbacks;
1065         }
1066 
1067         @Override
1068         public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
1069             boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW);
1070             endGestureStateOverride(!isInOverview, true /*canceled*/);
1071         }
1072 
1073         @Override
1074         public void onRecentsAnimationFinished(RecentsAnimationController controller) {
1075             endGestureStateOverride(!controller.getFinishTargetIsLauncher(),
1076                     controller.getLauncherIsVisibleAtFinish(), false /*canceled*/);
1077         }
1078 
1079         private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
1080             endGestureStateOverride(finishedToApp, finishedToApp, canceled);
1081         }
1082 
1083         /**
1084          * Handles whatever cleanup is needed after the recents animation is completed.
1085          * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled
1086          * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)}
1087          *
1088          * @param finishedToApp {@code true} if the recents animation finished to showing an app and
1089          *                      not workspace or overview
1090          * @param launcherIsVisible {code true} if launcher is visible at finish
1091          * @param canceled      {@code true} if the recents animation was canceled instead of
1092          *                      finishing
1093          *                      to completion
1094          */
1095         private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible,
1096                 boolean canceled) {
1097             mCallbacks.removeListener(this);
1098             mTaskBarRecentsAnimationListener = null;
1099             RecentsView recentsView = mControllers.uiController.getRecentsView();
1100             if (recentsView != null) {
1101                 recentsView.setTaskLaunchListener(null);
1102             }
1103 
1104             if (mSkipNextRecentsAnimEnd && !canceled) {
1105                 mSkipNextRecentsAnimEnd = false;
1106                 return;
1107             }
1108             updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible);
1109         }
1110     }
1111 
1112     /**
1113      * @see #updateStateForUserFinishedToApp(boolean, boolean)
1114      */
1115     private void updateStateForUserFinishedToApp(boolean finishedToApp) {
1116         updateStateForUserFinishedToApp(finishedToApp, !finishedToApp);
1117     }
1118 
1119     /**
1120      * Updates the visible state immediately to ensure a seamless handoff.
1121      *
1122      * @param finishedToApp True iff user is in an app.
1123      * @param launcherIsVisible True iff launcher is still visible (ie. transparent app)
1124      */
1125     private void updateStateForUserFinishedToApp(boolean finishedToApp,
1126             boolean launcherIsVisible) {
1127         // Update the visible state immediately to ensure a seamless handoff
1128         boolean launcherVisible = !finishedToApp || launcherIsVisible;
1129         updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
1130         updateStateForFlag(FLAG_VISIBLE, launcherVisible);
1131         applyState();
1132 
1133         TaskbarStashController controller = mControllers.taskbarStashController;
1134         if (DEBUG) {
1135             Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
1136         }
1137         controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible);
1138         controller.applyState();
1139     }
1140 
1141     /**
1142      * Helper function to run a callback on the RecentsWindowManager (if it exists).
1143      */
1144     private void runForRecentsWindowManager(Consumer<RecentsWindowManager> callback) {
1145         if (RecentsWindowFlags.getEnableOverviewInWindow()) {
1146             final TaskbarActivityContext taskbarContext = mControllers.taskbarActivityContext;
1147             RecentsWindowManager recentsWindowManager = RecentsDisplayModel.getINSTANCE()
1148                     .get(taskbarContext).getRecentsWindowManager(taskbarContext.getDisplayId());
1149             if (recentsWindowManager != null) {
1150                 callback.accept(recentsWindowManager);
1151             }
1152         }
1153     }
1154 
1155     private static String getStateString(int flags) {
1156         StringJoiner result = new StringJoiner("|");
1157         appendFlag(result, flags, FLAG_VISIBLE, "flag_visible");
1158         appendFlag(result, flags, FLAG_TRANSITION_TO_VISIBLE, "transition_to_visible");
1159         appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION,
1160                 "launcher_in_state_transition");
1161         appendFlag(result, flags, FLAG_AWAKE, "awake");
1162         appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
1163                 "was_active_while_awake");
1164         appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked");
1165         appendFlag(result, flags, FLAG_TASKBAR_HIDDEN, "taskbar_hidden");
1166         return result.toString();
1167     }
1168 
1169     protected void dumpLogs(String prefix, PrintWriter pw) {
1170         pw.println(prefix + "TaskbarLauncherStateController:");
1171         pw.println(String.format(
1172                 "%s\tmIconAlignment=%.2f",
1173                 prefix,
1174                 mIconAlignment.value));
1175         pw.println(String.format(
1176                 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
1177         pw.println(String.format(
1178                 "%s\tmTaskbarAlphaForHome=%.2f", prefix, mTaskbarAlphaForHome.getValue()));
1179         pw.println(String.format("%s\tmPrevState=%s", prefix,
1180                 mPrevState == null ? null : getStateString(mPrevState)));
1181         pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
1182         pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
1183         pw.println(String.format(
1184                 "%s\tmIsAnimatingToLauncher=%b",
1185                 prefix,
1186                 mIsAnimatingToLauncher));
1187         pw.println(String.format(
1188                 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
1189     }
1190 }
1191