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