• 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.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
19 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
20 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
21 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
22 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
23 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
24 import static com.android.systemui.animation.Interpolators.EMPHASIZED;
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                     applyState();
206                     boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
207                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
208                             mControllers, finalState.disallowTaskbarGlobalDrag(),
209                             disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
210                 }
211             };
212 
init(TaskbarControllers controllers, QuickstepLauncher launcher)213     public void init(TaskbarControllers controllers, QuickstepLauncher launcher) {
214         mCanSyncViews = false;
215 
216         mControllers = controllers;
217         mLauncher = launcher;
218 
219         mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline;
220 
221         mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController
222                 .getTaskbarBackgroundAlpha();
223         mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha();
224         mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness();
225         mIconAlphaForHome = mControllers.taskbarViewController
226                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
227 
228         resetIconAlignment();
229 
230         mLauncher.getStateManager().addStateListener(mStateListener);
231         mLauncherState = launcher.getStateManager().getState();
232         applyState(0);
233 
234         mCanSyncViews = true;
235         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
236     }
237 
onDestroy()238     public void onDestroy() {
239         mCanSyncViews = false;
240 
241         mIconAlignment.finishAnimation();
242 
243         Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)");
244         mLauncher.getHotseat().setIconsAlpha(1f);
245         mLauncher.getStateManager().removeStateListener(mStateListener);
246 
247         mCanSyncViews = true;
248         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
249     }
250 
251     /**
252      * Creates a transition animation to the launcher activity.
253      *
254      * Warning: the resulting animation must be played, since this method has side effects on this
255      * controller's state.
256      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)257     public Animator createAnimToLauncher(@NonNull LauncherState toState,
258             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
259         // If going to overview, stash the task bar
260         // If going home, align the icons to hotseat
261         AnimatorSet animatorSet = new AnimatorSet();
262 
263         // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
264         TaskbarStashController stashController = mControllers.taskbarStashController;
265         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE,
266                 toState.isTaskbarStashed(mLauncher));
267         if (DEBUG) {
268             Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false);
269         }
270         stashController.updateStateForFlag(FLAG_IN_APP, false);
271 
272         updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true);
273         animatorSet.play(stashController.createApplyStateAnimator(duration));
274         animatorSet.play(applyState(duration, false));
275 
276         if (mTaskBarRecentsAnimationListener != null) {
277             mTaskBarRecentsAnimationListener.endGestureStateOverride(
278                     !mLauncher.isInState(LauncherState.OVERVIEW));
279         }
280         mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
281         callbacks.addListener(mTaskBarRecentsAnimationListener);
282         ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
283                 mTaskBarRecentsAnimationListener.endGestureStateOverride(true));
284         return animatorSet;
285     }
286 
isAnimatingToLauncher()287     public boolean isAnimatingToLauncher() {
288         return mIsAnimatingToLauncher;
289     }
290 
setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)291     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
292         if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) {
293             // Animate the animation we have delayed immediately. This is usually triggered when
294             // the user has released their finger.
295             applyState();
296         }
297         mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim;
298     }
299 
300     /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)301     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
302         final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
303         final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
304 
305         updateStateForFlag(FLAG_AWAKE, currIsAwake);
306         if (prevIsAwake != currIsAwake) {
307             // The screen is switching between on/off. When turning off, capture whether the
308             // launcher is active and memoize this state.
309             updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
310                     prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE));
311         }
312 
313         boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
314         updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
315 
316         // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
317         // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
318         // when the device is asleep, the second condition extends ensures that the transition from
319         // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
320         // hide/reveal animation timings.
321         boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
322                 || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
323         updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
324 
325         if (skipAnim) {
326             applyState(0);
327         } else {
328             applyState();
329         }
330     }
331 
332     /**
333      * Updates the proper flag to change the state of the task bar.
334      *
335      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
336      *
337      * @param flag    The flag to update.
338      * @param enabled Whether to enable the flag
339      */
updateStateForFlag(int flag, boolean enabled)340     public void updateStateForFlag(int flag, boolean enabled) {
341         if (enabled) {
342             mState |= flag;
343         } else {
344             mState &= ~flag;
345         }
346     }
347 
hasAnyFlag(int flagMask)348     private boolean hasAnyFlag(int flagMask) {
349         return hasAnyFlag(mState, flagMask);
350     }
351 
hasAnyFlag(int flags, int flagMask)352     private boolean hasAnyFlag(int flags, int flagMask) {
353         return (flags & flagMask) != 0;
354     }
355 
applyState()356     public void applyState() {
357         applyState(mControllers.taskbarStashController.getStashDuration());
358     }
359 
applyState(long duration)360     public void applyState(long duration) {
361         applyState(duration, true);
362     }
363 
applyState(boolean start)364     public Animator applyState(boolean start) {
365         return applyState(mControllers.taskbarStashController.getStashDuration(), start);
366     }
367 
applyState(long duration, boolean start)368     public Animator applyState(long duration, boolean start) {
369         if (mControllers.taskbarActivityContext.isDestroyed()) {
370             return null;
371         }
372         Animator animator = null;
373         if (mPrevState == null || mPrevState != mState) {
374             // If this is our initial state, treat all flags as changed.
375             int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState;
376 
377             if (DEBUG) {
378                 String stateString;
379                 if (mPrevState == null) {
380                     stateString = getStateString(mState) + "(initial update)";
381                 } else {
382                     stateString = formatFlagChange(mState, mPrevState,
383                             TaskbarLauncherStateController::getStateString);
384                 }
385                 Log.d(TAG, "applyState: " + stateString
386                         + ", duration: " + duration
387                         + ", start: " + start);
388             }
389             mPrevState = mState;
390             animator = onStateChangeApplied(changedFlags, duration, start);
391         }
392         return animator;
393     }
394 
onStateChangeApplied(int changedFlags, long duration, boolean start)395     private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
396         final boolean isInLauncher = isInLauncher();
397         final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
398         final float toAlignment = isIconAlignedWithHotseat ? 1 : 0;
399         boolean handleOpenFloatingViews = false;
400         if (DEBUG) {
401             Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher
402                     + ", mLauncherState: " + mLauncherState
403                     + ", toAlignment: " + toAlignment);
404         }
405         AnimatorSet animatorSet = new AnimatorSet();
406 
407         if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) {
408             boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION);
409             playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted);
410 
411             if (launcherTransitionCompleted
412                     && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) {
413                 // We're about to be paused, set immediately to ensure seamless handoff.
414                 updateStateForFlag(FLAG_RESUMED, false);
415                 applyState(0 /* duration */);
416             }
417             if (mLauncherState == LauncherState.NORMAL) {
418                 // We're changing state to home, should close open popups e.g. Taskbar AllApps
419                 handleOpenFloatingViews = true;
420             }
421         }
422 
423         if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE | FLAG_AWAKE)) {
424             animatorSet.addListener(new AnimatorListenerAdapter() {
425                 @Override
426                 public void onAnimationStart(Animator animation) {
427                     mIsAnimatingToLauncher = isInLauncher;
428 
429                     TaskbarStashController stashController =
430                             mControllers.taskbarStashController;
431                     if (DEBUG) {
432                         Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
433                     }
434                     stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher);
435                     stashController.applyState(duration);
436                 }
437 
438                 @Override
439                 public void onAnimationEnd(Animator animation) {
440                     mIsAnimatingToLauncher = false;
441                 }
442             });
443 
444             // Handle closing open popups when going home/overview
445             handleOpenFloatingViews = true;
446         }
447 
448         if (handleOpenFloatingViews && isInLauncher) {
449             AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext);
450         }
451 
452         if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
453             // Take note of the current time, as the taskbar is made visible again.
454             mLastUnlockTimeMs = SystemClock.elapsedRealtime();
455         }
456 
457         boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
458         float taskbarAlpha = isHidden ? 0 : 1;
459         if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) {
460             Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha);
461 
462             taskbarVisibility.setDuration(duration);
463             if (isHidden) {
464                 // Stash the transient taskbar once the taskbar is not visible. This reduces
465                 // visual noise when unlocking the device afterwards.
466                 animatorSet.addListener(new AnimatorListenerAdapter() {
467                     @Override
468                     public void onAnimationEnd(Animator animation) {
469                         TaskbarStashController stashController =
470                                 mControllers.taskbarStashController;
471                         stashController.updateAndAnimateTransientTaskbar(
472                                 /* stash */ true, /* duration */ 0);
473                     }
474                 });
475             } else {
476                 // delay the fade in animation a bit to reduce visual noise when waking up a device
477                 // with a fingerprint reader. This should only be done when the device was woken
478                 // up via fingerprint reader, however since this information is currently not
479                 // available, opting to always delay the fade-in a bit.
480                 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
481                 taskbarVisibility.setStartDelay(
482                         Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
483             }
484             animatorSet.play(taskbarVisibility);
485         }
486 
487         float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
488 
489         // Don't animate if background has reached desired value.
490         if (mTaskbarBackgroundAlpha.isAnimating()
491                 || mTaskbarBackgroundAlpha.value != backgroundAlpha) {
492             mTaskbarBackgroundAlpha.cancelAnimation();
493             if (DEBUG) {
494                 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
495                         + mTaskbarBackgroundAlpha.value
496                         + " -> " + backgroundAlpha + ": " + duration);
497             }
498 
499             boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat;
500             boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat;
501             boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
502 
503             float startDelay = 0;
504             // We want to delay the background from fading in so that the icons have time to move
505             // into the bounds of the background before it appears.
506             if (isInLauncherIconNotAligned) {
507                 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
508             } else if (notInLauncherIconNotAligned) {
509                 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
510             }
511             float newDuration = duration - startDelay;
512             if (isInLauncherIconIsAligned) {
513                 // Make the background fade out faster so that it is gone by the time the
514                 // icons move outside of the bounds of the background.
515                 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;
516             }
517             Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
518                     .animateToValue(backgroundAlpha)
519                     .setDuration((long) newDuration);
520             taskbarBackgroundAlpha.setStartDelay((long) startDelay);
521             animatorSet.play(taskbarBackgroundAlpha);
522         }
523 
524         float cornerRoundness = isInLauncher ? 0 : 1;
525 
526         // Don't animate if corner roundness has reached desired value.
527         if (mTaskbarCornerRoundness.isAnimating()
528                 || mTaskbarCornerRoundness.value != cornerRoundness) {
529             mTaskbarCornerRoundness.cancelAnimation();
530             if (DEBUG) {
531                 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - "
532                         + mTaskbarCornerRoundness.value
533                         + " -> " + cornerRoundness + ": " + duration);
534             }
535             animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness));
536         }
537 
538         // Keep isUnlockTransition in sync with its counterpart in
539         // TaskbarStashController#createAnimToIsStashed.
540         boolean isUnlockTransition =
541                 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
542         if (isUnlockTransition) {
543             // When transitioning to unlocked, ensure the hotseat is fully visible from the
544             // beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
545             mIconAlignment.cancelAnimation();
546             // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual
547             // change in value
548             mIconAlignment.updateValue(toAlignment);
549         } else if (mIconAlignment.isAnimatingToValue(toAlignment)
550                 || mIconAlignment.isSettledOnValue(toAlignment)) {
551             // Already at desired value, but make sure we run the callback at the end.
552             animatorSet.addListener(AnimatorListeners.forEndCallback(
553                     this::onIconAlignmentRatioChanged));
554         } else {
555             mIconAlignment.cancelAnimation();
556             ObjectAnimator iconAlignAnim = mIconAlignment
557                     .animateToValue(toAlignment)
558                     .setDuration(duration);
559             if (DEBUG) {
560                 Log.d(TAG, "onStateChangeApplied - iconAlignment - "
561                         + mIconAlignment.value
562                         + " -> " + toAlignment + ": " + duration);
563             }
564             animatorSet.play(iconAlignAnim);
565         }
566 
567         animatorSet.setInterpolator(EMPHASIZED);
568 
569         if (start) {
570             animatorSet.start();
571         }
572         return animatorSet;
573     }
574 
575     /**
576      * Whether the taskbar is aligned with the hotseat in the current/target launcher state.
577      *
578      * This refers to the intended state - a transition to this state might be in progress.
579      */
isTaskbarAlignedWithHotseat()580     public boolean isTaskbarAlignedWithHotseat() {
581         return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
582     }
583 
584     /**
585      * Returns if icons should be aligned to hotseat in the current transition
586      */
isIconAlignedWithHotseat()587     public boolean isIconAlignedWithHotseat() {
588         if (isInLauncher()) {
589             boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
590             boolean willStashVisually = isInStashedState
591                     && mControllers.taskbarStashController.supportsVisualStashing();
592             boolean isTaskbarAlignedWithHotseat =
593                     mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
594             return isTaskbarAlignedWithHotseat && !willStashVisually;
595         } else {
596             return false;
597         }
598     }
599 
600     /**
601      * Returns if the current Launcher state has hotseat on top of other elemnets.
602      */
isInHotseatOnTopStates()603     public boolean isInHotseatOnTopStates() {
604         return mLauncherState != LauncherState.ALL_APPS;
605     }
606 
isInOverview()607     boolean isInOverview() {
608         return mLauncherState == LauncherState.OVERVIEW;
609     }
610 
playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)611     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
612             boolean committed) {
613         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
614         TaskbarStashController stashController = mControllers.taskbarStashController;
615         stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState);
616         Animator stashAnimator = stashController.createApplyStateAnimator(duration);
617         if (stashAnimator != null) {
618             stashAnimator.addListener(new AnimatorListenerAdapter() {
619                 @Override
620                 public void onAnimationEnd(Animator animation) {
621                     if (isInStashedState && committed) {
622                         // Reset hotseat alpha to default
623                         Log.d("b/260135164",
624                                 "playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)");
625                         mLauncher.getHotseat().setIconsAlpha(1);
626                     }
627                 }
628 
629                 @Override
630                 public void onAnimationStart(Animator animation) {
631                     if (mLauncher.getHotseat().getIconsAlpha() > 0) {
632                         updateIconAlphaForHome(mLauncher.getHotseat().getIconsAlpha());
633                     }
634                 }
635             });
636             animatorSet.play(stashAnimator);
637         }
638 
639         // Translate back to 0 at a shorter or same duration as the icon alignment animation.
640         // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
641         // overview to home. When not in app, we do duration / 2 just to make it feel snappier.
642         long resetDuration = mControllers.taskbarStashController.isInApp()
643                 ? duration
644                 : duration / 2;
645         if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
646                 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
647             animatorSet.play(mControllers.taskbarTranslationController
648                     .createAnimToResetTranslation(resetDuration));
649         }
650     }
651 
652     /** Whether the launcher is considered active. */
isInLauncher()653     private boolean isInLauncher() {
654         if (hasAnyFlag(FLAG_AWAKE)) {
655             return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE);
656         } else {
657             return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE);
658         }
659     }
660 
661     /**
662      * Resets and updates the icon alignment.
663      */
resetIconAlignment()664     protected void resetIconAlignment() {
665         mIconAlignment.finishAnimation();
666         onIconAlignmentRatioChanged();
667     }
668 
onIconAlignmentRatioChanged()669     private void onIconAlignmentRatioChanged() {
670         float currentValue = mIconAlphaForHome.getValue();
671         boolean taskbarWillBeVisible = mIconAlignment.value < 1;
672         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
673                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
674 
675         mControllers.taskbarViewController.setLauncherIconAlignment(
676                 mIconAlignment.value, mLauncher.getDeviceProfile());
677         mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
678         // Switch taskbar and hotseat in last frame
679         updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
680 
681         // Sync the first frame where we swap taskbar and hotseat.
682         if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
683             ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
684                     mControllers.taskbarActivityContext.getDragLayer(),
685                     () -> {
686                     });
687         }
688     }
689 
updateIconAlphaForHome(float alpha)690     private void updateIconAlphaForHome(float alpha) {
691         if (mControllers.taskbarActivityContext.isDestroyed()) {
692             Log.e("b/260135164", "updateIconAlphaForHome is called after Taskbar is destroyed",
693                     new Exception());
694             return;
695         }
696         mIconAlphaForHome.setValue(alpha);
697         boolean hotseatVisible = alpha == 0
698                 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
699                 && mIconAlignment.value > 0);
700         /*
701          * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
702          * should not be visible at the same time.
703          */
704         Log.d("b/260135164",
705                 "updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0)
706                         + "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent);
707         mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
708         if (mIsQsbInline) {
709             mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
710         }
711     }
712 
713     private final class TaskBarRecentsAnimationListener implements
714             RecentsAnimationCallbacks.RecentsAnimationListener {
715         private final RecentsAnimationCallbacks mCallbacks;
716 
TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks)717         TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) {
718             mCallbacks = callbacks;
719         }
720 
721         @Override
onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)722         public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
723             boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW);
724             endGestureStateOverride(!isInOverview);
725         }
726 
727         @Override
onRecentsAnimationFinished(RecentsAnimationController controller)728         public void onRecentsAnimationFinished(RecentsAnimationController controller) {
729             endGestureStateOverride(!controller.getFinishTargetIsLauncher());
730         }
731 
endGestureStateOverride(boolean finishedToApp)732         private void endGestureStateOverride(boolean finishedToApp) {
733             mCallbacks.removeListener(this);
734             mTaskBarRecentsAnimationListener = null;
735             ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
736 
737             // Update the resumed state immediately to ensure a seamless handoff
738             boolean launcherResumed = !finishedToApp;
739             updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false);
740             updateStateForFlag(FLAG_RESUMED, launcherResumed);
741             applyState();
742 
743             TaskbarStashController controller = mControllers.taskbarStashController;
744             if (DEBUG) {
745                 Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
746             }
747             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
748             controller.applyState();
749         }
750     }
751 
getStateString(int flags)752     private static String getStateString(int flags) {
753         StringJoiner result = new StringJoiner("|");
754         appendFlag(result, flags, FLAG_RESUMED, "resumed");
755         appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed");
756         appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION,
757                 "launcher_in_state_transition");
758         appendFlag(result, flags, FLAG_AWAKE, "awake");
759         appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE,
760                 "was_active_while_awake");
761         appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked");
762         return result.toString();
763     }
764 
dumpLogs(String prefix, PrintWriter pw)765     protected void dumpLogs(String prefix, PrintWriter pw) {
766         pw.println(prefix + "TaskbarLauncherStateController:");
767         pw.println(String.format(
768                 "%s\tmIconAlignment=%.2f",
769                 prefix,
770                 mIconAlignment.value));
771         pw.println(String.format(
772                 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
773         pw.println(String.format(
774                 "%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue()));
775         pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState)));
776         pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
777         pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
778         pw.println(String.format(
779                 "%s\tmIsAnimatingToLauncher=%b",
780                 prefix,
781                 mIsAnimatingToLauncher));
782         pw.println(String.format(
783                 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim));
784     }
785 }
786