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 android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
19 
20 import static com.android.app.animation.Interpolators.EMPHASIZED;
21 import static com.android.app.animation.Interpolators.FINAL_FRAME;
22 import static com.android.app.animation.Interpolators.INSTANT;
23 import static com.android.app.animation.Interpolators.LINEAR;
24 import static com.android.internal.jank.InteractionJankMonitor.Configuration;
25 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
26 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
29 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
30 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
31 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
32 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
36 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
37 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
38 
39 import android.animation.Animator;
40 import android.animation.AnimatorListenerAdapter;
41 import android.animation.AnimatorSet;
42 import android.animation.ValueAnimator;
43 import android.app.RemoteAction;
44 import android.graphics.drawable.Icon;
45 import android.os.SystemClock;
46 import android.util.Log;
47 import android.view.InsetsController;
48 import android.view.View;
49 import android.view.ViewConfiguration;
50 import android.view.accessibility.AccessibilityManager;
51 import android.view.animation.Interpolator;
52 
53 import androidx.annotation.IntDef;
54 import androidx.annotation.NonNull;
55 import androidx.annotation.Nullable;
56 import androidx.annotation.VisibleForTesting;
57 
58 import com.android.internal.jank.InteractionJankMonitor;
59 import com.android.launcher3.Alarm;
60 import com.android.launcher3.DeviceProfile;
61 import com.android.launcher3.R;
62 import com.android.launcher3.anim.AnimatedFloat;
63 import com.android.launcher3.anim.AnimatorListeners;
64 import com.android.launcher3.statehandlers.DesktopVisibilityController;
65 import com.android.launcher3.util.DisplayController;
66 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
67 import com.android.quickstep.LauncherActivityInterface;
68 import com.android.quickstep.SystemUiProxy;
69 import com.android.quickstep.util.SystemUiFlagUtils;
70 
71 import java.io.PrintWriter;
72 import java.lang.annotation.Retention;
73 import java.lang.annotation.RetentionPolicy;
74 import java.util.StringJoiner;
75 import java.util.function.LongPredicate;
76 
77 /**
78  * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
79  * create a cohesive animation between stashed/unstashed states.
80  */
81 public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
82     private static final String TAG = "TaskbarStashController";
83     private static final boolean DEBUG = false;
84 
85     public static final int FLAG_IN_APP = 1 << 0;
86     public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
87     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
88     public static final int FLAG_STASHED_IME = 1 << 3; // IME is visible
89     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 4;
90     public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 5; // All apps is visible.
91     public static final int FLAG_IN_SETUP = 1 << 6; // In the Setup Wizard
92     public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 7; // phone screen gesture nav, stashed
93     public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 8; // Autohide (transient taskbar).
94     public static final int FLAG_STASHED_SYSUI = 1 << 9; //  app pinning,...
95     public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
96     public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
97 
98     // If any of these flags are enabled, isInApp should return true.
99     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
100 
101     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
102     private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_SYSUI
103             | FLAG_STASHED_IN_APP_SETUP | FLAG_STASHED_IN_TASKBAR_ALL_APPS
104             | FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO | FLAG_STASHED_IME;
105 
106     // If we're in overview and any of these flags are enabled, taskbar should be stashed.
107     private static final int FLAGS_STASHED_IN_OVERVIEW = FLAG_STASHED_IME;
108 
109     // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
110     // height. This way the reported insets are consistent even during transitions out of the app.
111     // Currently any flag that causes us to stash in an app is included, except for IME or All Apps
112     // since those cover the underlying app anyway and thus the app shouldn't change insets.
113     private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
114             & ~FLAG_STASHED_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS & ~FLAG_STASHED_IN_APP_SYSUI;
115 
116     // If any of these flags are enabled, the taskbar must be stashed.
117     private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
118             | FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN;
119 
120     /**
121      * How long to stash/unstash when manually invoked via long press.
122      *
123      * Use {@link #getStashDuration()} to query duration
124      */
125     private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
126 
127     /**
128      * How long to stash/unstash transient taskbar.
129      *
130      * Use {@link #getStashDuration()} to query duration.
131      */
132     private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
133 
134     /**
135      * How long to stash/unstash when keyboard is appearing/disappearing.
136      */
137     private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
138 
139     /**
140      * The scale TaskbarView animates to when being stashed.
141      */
142     protected static final float STASHED_TASKBAR_SCALE = 0.5f;
143 
144     /**
145      * How long the hint animation plays, starting on motion down.
146      */
147     private static final long TASKBAR_HINT_STASH_DURATION =
148             ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
149 
150     /**
151      * How long to delay the icon/stash handle alpha.
152      */
153     private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
154 
155     /**
156      * How long the icon/stash handle alpha animation plays.
157      */
158     private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
159 
160     /**
161      * How long to delay the icon/stash handle alpha for the home to app taskbar animation.
162      */
163     private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
164 
165     /**
166      * The scale that the stashed handle animates to when hinting towards the unstashed state.
167      */
168     private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
169 
170     /**
171      * Whether taskbar should be stashed out of the box.
172      */
173     private static final boolean DEFAULT_STASHED_PREF = false;
174 
175     // Auto stashes when user has not interacted with the Taskbar after X ms.
176     private static final long NO_TOUCH_TIMEOUT_TO_STASH_MS = 5000;
177 
178     // Duration for which an unlock event is considered "current", as other events are received
179     // asynchronously.
180     private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
181 
182     /**
183      * The default stash animation, morphing the taskbar into the navbar.
184      */
185     private static final int TRANSITION_DEFAULT = 0;
186     /**
187      * Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in internal
188      * animation timings.
189      */
190     private static final int TRANSITION_HOME_TO_APP = 1;
191     /**
192      * Fading the navbar in and out, where the taskbar jumpcuts in and out at the very begin/end of
193      * the transition. Used to transition between the hotseat and navbar` without the stash/unstash
194      * transition.
195      */
196     private static final int TRANSITION_HANDLE_FADE = 2;
197     /**
198      * Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to hotseat
199      * animation" bound to the progress of a swipe gesture. It differs from TRANSITION_DEFAULT
200      * by not scaling the height of the taskbar background.
201      */
202     private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
203 
204     @Retention(RetentionPolicy.SOURCE)
205     @IntDef(value = {
206             TRANSITION_DEFAULT,
207             TRANSITION_HOME_TO_APP,
208             TRANSITION_HANDLE_FADE,
209             TRANSITION_UNSTASH_SUW_MANUAL,
210     })
211     private @interface StashAnimation {
212     }
213 
214     private final TaskbarActivityContext mActivity;
215     private final int mStashedHeight;
216     private final int mUnstashedHeight;
217     private final SystemUiProxy mSystemUiProxy;
218 
219     // Initialized in init.
220     private TaskbarControllers mControllers;
221     // Taskbar background properties.
222     private AnimatedFloat mTaskbarBackgroundOffset;
223     private AnimatedFloat mTaskbarImeBgAlpha;
224     private MultiProperty mTaskbarBackgroundAlphaForStash;
225     // TaskbarView icon properties.
226     private MultiProperty mIconAlphaForStash;
227     private AnimatedFloat mIconScaleForStash;
228     private AnimatedFloat mIconTranslationYForStash;
229     // Stashed handle properties.
230     private MultiProperty mTaskbarStashedHandleAlpha;
231     private AnimatedFloat mTaskbarStashedHandleHintScale;
232     private final AccessibilityManager mAccessibilityManager;
233 
234     /** Whether we are currently visually stashed (might change based on launcher state). */
235     private boolean mIsStashed = false;
236     private long mState;
237 
238     private @Nullable AnimatorSet mAnimator;
239     private boolean mIsSystemGestureInProgress;
240     private boolean mIsImeShowing;
241     private boolean mIsImeSwitcherShowing;
242 
243     private final Alarm mTimeoutAlarm = new Alarm();
244     private boolean mEnableBlockingTimeoutDuringTests = false;
245 
246     private Animator mTaskbarBackgroundAlphaAnimator;
247     private long mTaskbarBackgroundDuration;
248     private boolean mIsGoingHome;
249 
250     // Evaluate whether the handle should be stashed
251     private final LongPredicate mIsStashedPredicate = flags -> {
252         boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
253         boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
254         boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
255         boolean inOverview = hasAnyFlag(flags, FLAG_IN_OVERVIEW);
256         boolean stashedInOverview = hasAnyFlag(flags, FLAGS_STASHED_IN_OVERVIEW);
257         boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED);
258         return (inApp && stashedInApp)
259                 || (!inApp && stashedLauncherState)
260                 || (inOverview && stashedInOverview)
261                 || forceStashed;
262     };
263     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
264             mIsStashedPredicate);
265 
266     private boolean mIsTaskbarSystemActionRegistered = false;
267     private TaskbarSharedState mTaskbarSharedState;
268 
TaskbarStashController(TaskbarActivityContext activity)269     public TaskbarStashController(TaskbarActivityContext activity) {
270         mActivity = activity;
271         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
272         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
273 
274         mTaskbarBackgroundDuration =
275                 activity.getResources().getInteger(R.integer.taskbar_background_duration);
276         if (mActivity.isPhoneMode()) {
277             mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
278                     R.dimen.taskbar_phone_size);
279             mStashedHeight = mActivity.getResources().getDimensionPixelSize(
280                     R.dimen.taskbar_stashed_size);
281         } else {
282             mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight;
283             mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight;
284         }
285     }
286 
287     /**
288      * Show Taskbar upon receiving broadcast
289      */
showTaskbarFromBroadcast()290     public void showTaskbarFromBroadcast() {
291         // If user is in middle of taskbar education handle go to next step of education
292         if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) {
293             mControllers.taskbarEduTooltipController.hide();
294             mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
295         }
296         updateAndAnimateTransientTaskbar(false);
297     }
298 
299     /**
300      * Initializes the controller
301      */
init( TaskbarControllers controllers, boolean setupUIVisible, TaskbarSharedState sharedState)302     public void init(
303             TaskbarControllers controllers,
304             boolean setupUIVisible,
305             TaskbarSharedState sharedState) {
306         mControllers = controllers;
307         mTaskbarSharedState = sharedState;
308 
309         TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
310         mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
311         mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
312         mTaskbarBackgroundAlphaForStash = dragLayerController.getBackgroundRendererAlphaForStash();
313 
314         TaskbarViewController taskbarViewController = controllers.taskbarViewController;
315         mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get(
316                 TaskbarViewController.ALPHA_INDEX_STASH);
317         mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
318         mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
319 
320         StashedHandleViewController stashedHandleController =
321                 controllers.stashedHandleViewController;
322         mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().get(
323                 StashedHandleViewController.ALPHA_INDEX_STASHED);
324         mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
325 
326         boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
327         boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
328         boolean isStashedInAppAuto =
329                 isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
330 
331         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
332             isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto;
333         }
334         updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto);
335         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
336         updateStateForFlag(FLAG_IN_SETUP, isInSetup);
337         updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneGestureNavMode());
338         // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
339         // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
340         updateStateForFlag(FLAG_IN_APP, true);
341         applyState(/* duration = */ 0);
342         if (mTaskbarSharedState.getTaskbarWasPinned()
343                 || !mTaskbarSharedState.taskbarWasStashedAuto) {
344             tryStartTaskbarTimeout();
345         }
346         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
347     }
348 
349     /**
350      * Returns whether the taskbar can visually stash into a handle based on the current device
351      * state.
352      */
supportsVisualStashing()353     public boolean supportsVisualStashing() {
354         return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing();
355     }
356 
357     /**
358      * Enables the auto timeout for taskbar stashing. This method should only be used for taskbar
359      * testing.
360      */
361     @VisibleForTesting
enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout)362     public void enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout) {
363         mEnableBlockingTimeoutDuringTests = enableBlockingTimeout;
364     }
365 
366     /**
367      * Sets the flag indicating setup UI is visible
368      */
setSetupUIVisible(boolean isVisible)369     protected void setSetupUIVisible(boolean isVisible) {
370         boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
371         updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
372         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
373         applyState(hideTaskbar ? 0 : getStashDuration());
374     }
375 
376     /**
377      * Returns how long the stash/unstash animation should play.
378      */
getStashDuration()379     public long getStashDuration() {
380         return DisplayController.isTransientTaskbar(mActivity)
381                 ? TRANSIENT_TASKBAR_STASH_DURATION
382                 : TASKBAR_STASH_DURATION;
383     }
384 
385     /**
386      * Returns whether the taskbar is currently visually stashed.
387      */
isStashed()388     public boolean isStashed() {
389         return mIsStashed;
390     }
391 
392     /**
393      * Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
394      */
isStashedInApp()395     public boolean isStashedInApp() {
396         return hasAnyFlag(FLAGS_STASHED_IN_APP);
397     }
398 
399     /**
400      * Returns whether the taskbar should be stashed in the current LauncherState.
401      */
isInStashedLauncherState()402     public boolean isInStashedLauncherState() {
403         return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing());
404     }
405 
hasAnyFlag(long flagMask)406     private boolean hasAnyFlag(long flagMask) {
407         return hasAnyFlag(mState, flagMask);
408     }
409 
hasAnyFlag(long flags, long flagMask)410     private boolean hasAnyFlag(long flags, long flagMask) {
411         return (flags & flagMask) != 0;
412     }
413 
414 
415     /**
416      * Returns whether the taskbar is currently visible and not in the process of being stashed.
417      */
isTaskbarVisibleAndNotStashing()418     public boolean isTaskbarVisibleAndNotStashing() {
419         return !mIsStashed && mControllers.taskbarViewController.areIconsVisible();
420     }
421 
isInApp()422     public boolean isInApp() {
423         return hasAnyFlag(FLAGS_IN_APP);
424     }
425 
426     /**
427      * Returns the height that taskbar will be touchable.
428      */
getTouchableHeight()429     public int getTouchableHeight() {
430         return mIsStashed
431                 ? mStashedHeight
432                 : (mUnstashedHeight + mActivity.getDeviceProfile().taskbarBottomMargin);
433     }
434 
435     /**
436      * Returns the height that taskbar will inset when inside apps.
437      *
438      * @see android.view.WindowInsets.Type#navigationBars()
439      * @see android.view.WindowInsets.Type#systemBars()
440      */
getContentHeightToReportToApps()441     public int getContentHeightToReportToApps() {
442         if (mActivity.isUserSetupComplete() && (mActivity.isPhoneGestureNavMode()
443                 || DisplayController.isTransientTaskbar(mActivity))) {
444             return getStashedHeight();
445         }
446 
447         if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
448             DeviceProfile dp = mActivity.getDeviceProfile();
449             if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && (dp.isTaskbarPresent
450                     || mActivity.isPhoneGestureNavMode())) {
451                 // We always show the back button in SUW but in portrait the SUW layout may not
452                 // be wide enough to support overlapping the nav bar with its content.
453                 // We're sending different res values in portrait vs landscape
454                 return mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_suw_insets);
455             }
456             boolean isAnimating = mAnimator != null && mAnimator.isStarted();
457             if (!mControllers.stashedHandleViewController.isStashedHandleVisible()
458                     && isInApp()
459                     && !isAnimating) {
460                 // We are in a settled state where we're not showing the handle even though taskbar
461                 // is stashed. This can happen for example when home button is disabled (see
462                 // StashedHandleViewController#setIsHomeButtonDisabled()).
463                 return 0;
464             }
465             return mStashedHeight;
466         }
467 
468         return mUnstashedHeight;
469     }
470 
471     /**
472      * Returns the height that taskbar will inset when inside apps.
473      *
474      * @see android.view.WindowInsets.Type#tappableElement()
475      */
getTappableHeightToReportToApps()476     public int getTappableHeightToReportToApps() {
477         int contentHeight = getContentHeightToReportToApps();
478         return contentHeight <= mStashedHeight ? 0 : contentHeight;
479     }
480 
getStashedHeight()481     public int getStashedHeight() {
482         return mStashedHeight;
483     }
484 
485     /**
486      * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
487      * If bubble bar exists, it will match taskbars stashing behavior.
488      */
updateAndAnimateTransientTaskbar(boolean stash)489     public void updateAndAnimateTransientTaskbar(boolean stash) {
490         updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
491     }
492 
493     /**
494      * Stash or unstashes the transient taskbar.
495      *
496      * @param stash               whether transient taskbar should be stashed.
497      * @param shouldBubblesFollow whether bubbles should match taskbars behavior.
498      */
updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow)499     public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
500         if (!DisplayController.isTransientTaskbar(mActivity)) {
501             return;
502         }
503 
504         if (
505                 stash
506                         && !mControllers.taskbarAutohideSuspendController
507                         .isSuspendedForTransientTaskbarInLauncher()
508                         && mControllers.taskbarAutohideSuspendController
509                         .isTransientTaskbarStashingSuspended()) {
510             // Avoid stashing if autohide is currently suspended.
511             return;
512         }
513 
514         if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
515             mTaskbarSharedState.taskbarWasStashedAuto = stash;
516             updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
517             applyState();
518         }
519 
520         mControllers.bubbleControllers.ifPresent(controllers -> {
521             if (shouldBubblesFollow) {
522                 final boolean willStash = mIsStashedPredicate.test(mState);
523                 if (willStash != controllers.bubbleStashController.isStashed()) {
524                     // Typically bubbles gets stashed / unstashed along with Taskbar, however, if
525                     // taskbar is becoming stashed because bubbles is being expanded, we don't want
526                     // to stash bubbles.
527                     if (willStash) {
528                         controllers.bubbleStashController.stashBubbleBar();
529                     } else {
530                         controllers.bubbleStashController.showBubbleBar(false /* expandBubbles */);
531                     }
532                 }
533             }
534         });
535     }
536 
537     /**
538      * Stashes transient taskbar after it has timed out.
539      */
updateAndAnimateTransientTaskbarForTimeout()540     private void updateAndAnimateTransientTaskbarForTimeout() {
541         // If bubbles are expanded we shouldn't stash them when taskbar is hidden
542         // for the timeout.
543         boolean bubbleBarExpanded = mControllers.bubbleControllers.isPresent()
544                 && mControllers.bubbleControllers.get().bubbleBarViewController.isExpanded();
545         updateAndAnimateTransientTaskbar(/* stash= */ true,
546                 /* shouldBubblesFollow= */ !bubbleBarExpanded);
547     }
548 
549     /** Toggles the Taskbar's stash state. */
toggleTaskbarStash()550     public void toggleTaskbarStash() {
551         if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
552         updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
553     }
554 
555     /**
556      * Adds the Taskbar unstash to Hotseat animator to the animator set.
557      *
558      * This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a
559      * swipe progress.
560      *
561      * @param placeholderDuration a placeholder duration to be used to ensure all full-length
562      *                            sub-animations are properly coordinated. This duration should not
563      *                            actually be used since this animation tracks a swipe progress.
564      */
addUnstashToHotseatAnimationFromSuw(AnimatorSet animation, int placeholderDuration)565     protected void addUnstashToHotseatAnimationFromSuw(AnimatorSet animation,
566             int placeholderDuration) {
567         // Defer any UI updates now to avoid the UI becoming stale when the animation plays.
568         mControllers.taskbarViewController.setDeferUpdatesForSUW(true);
569         createAnimToIsStashed(
570                 /* isStashed= */ mActivity.isPhoneMode(),
571                 placeholderDuration,
572                 TRANSITION_UNSTASH_SUW_MANUAL,
573                 /* jankTag= */ "SUW_MANUAL");
574         animation.addListener(AnimatorListeners.forEndCallback(
575                 () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false)));
576         animation.play(mAnimator);
577     }
578 
579     /**
580      * Create a stash animation and save to {@link #mAnimator}.
581      *
582      * @param isStashed     whether it's a stash animation or an unstash animation
583      * @param duration      duration of the animation
584      * @param animationType what transition type to play.
585      * @param jankTag       tag to be used in jank monitor trace.
586      */
createAnimToIsStashed(boolean isStashed, long duration, @StashAnimation int animationType, String jankTag)587     private void createAnimToIsStashed(boolean isStashed, long duration,
588             @StashAnimation int animationType, String jankTag) {
589         if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) {
590             // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation.
591             Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar");
592         }
593 
594         if (mAnimator != null) {
595             mAnimator.cancel();
596         }
597         mAnimator = new AnimatorSet();
598         addJankMonitorListener(
599                 mAnimator, /* expanding= */ !isStashed, /* tag= */ jankTag);
600         boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
601         final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar
602                 ? 0
603                 : (mUnstashedHeight - mStashedHeight);
604 
605         if (!supportsVisualStashing()) {
606             // Just hide/show the icons and background instead of stashing into a handle.
607             mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
608                     .setDuration(duration));
609             mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
610                     .setDuration(duration));
611             mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
612                             ? stashTranslation : 0)
613                     .setDuration(duration));
614             mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
615                     (hasAnyFlag(FLAG_STASHED_IME) && isStashed) ? 0 : 1).setDuration(
616                     duration));
617             mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
618                 mAnimator = null;
619                 mIsStashed = isStashed;
620                 onIsStashedChanged(mIsStashed);
621             }));
622             return;
623         }
624 
625         if (isTransientTaskbar) {
626             createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType);
627         } else {
628             createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
629         }
630 
631         mAnimator.addListener(new AnimatorListenerAdapter() {
632             @Override
633             public void onAnimationStart(Animator animation) {
634                 mIsStashed = isStashed;
635                 onIsStashedChanged(mIsStashed);
636 
637                 cancelTimeoutIfExists();
638             }
639 
640             @Override
641             public void onAnimationEnd(Animator animation) {
642                 mAnimator = null;
643 
644                 if (!mIsStashed) {
645                     tryStartTaskbarTimeout();
646                 }
647 
648                 // only announce if we are actually animating
649                 if (duration > 0 && isInApp()) {
650                     mControllers.taskbarViewController.announceForAccessibility();
651                 }
652             }
653         });
654     }
655 
createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, float stashTranslation, @StashAnimation int animationType)656     private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
657             float stashTranslation, @StashAnimation int animationType) {
658         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
659         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
660         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
661         AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
662 
663         final float firstHalfDurationScale;
664         final float secondHalfDurationScale;
665 
666         if (isStashed) {
667             firstHalfDurationScale = 0.75f;
668             secondHalfDurationScale = 0.5f;
669 
670             fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
671             fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1));
672 
673             firstHalfAnimatorSet.playTogether(
674                     mIconAlphaForStash.animateToValue(0),
675                     mIconScaleForStash.animateToValue(mActivity.isPhoneMode() ?
676                             0 : STASHED_TASKBAR_SCALE)
677             );
678             secondHalfAnimatorSet.playTogether(
679                     mTaskbarStashedHandleAlpha.animateToValue(1)
680             );
681 
682             if (animationType == TRANSITION_HANDLE_FADE) {
683                 fullLengthAnimatorSet.setInterpolator(INSTANT);
684                 firstHalfAnimatorSet.setInterpolator(INSTANT);
685             }
686         } else {
687             firstHalfDurationScale = 0.5f;
688             secondHalfDurationScale = 0.75f;
689 
690             fullLengthAnimatorSet.playTogether(
691                     mIconScaleForStash.animateToValue(1),
692                     mIconTranslationYForStash.animateToValue(0));
693 
694             final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
695             if (animateBg) {
696                 fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0));
697             } else {
698                 fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback(
699                         () -> mTaskbarBackgroundOffset.updateValue(0)));
700             }
701 
702             firstHalfAnimatorSet.playTogether(
703                     mTaskbarStashedHandleAlpha.animateToValue(0)
704             );
705             secondHalfAnimatorSet.playTogether(
706                     mIconAlphaForStash.animateToValue(1)
707             );
708 
709             if (animationType == TRANSITION_HANDLE_FADE) {
710                 fullLengthAnimatorSet.setInterpolator(FINAL_FRAME);
711                 secondHalfAnimatorSet.setInterpolator(FINAL_FRAME);
712             }
713         }
714 
715         fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
716                 .createRevealAnimToIsStashed(isStashed));
717         // Return the stashed handle to its default scale in case it was changed as part of the
718         // feedforward hint. Note that the reveal animation above also visually scales it.
719         fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
720 
721         fullLengthAnimatorSet.setDuration(duration);
722         firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
723         secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
724         secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
725 
726         as.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
727                 secondHalfAnimatorSet);
728 
729     }
730 
createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, @StashAnimation int animationType)731     private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
732             @StashAnimation int animationType) {
733         // Target values of the properties this is going to set
734         final float backgroundOffsetTarget = isStashed ? 1 : 0;
735         final float iconAlphaTarget = isStashed ? 0 : 1;
736         final float stashedHandleAlphaTarget = isStashed ? 1 : 0;
737         final float backgroundAlphaTarget = isStashed ? 0 : 1;
738 
739         // Timing for the alpha values depend on the animation played
740         long iconAlphaStartDelay = 0, iconAlphaDuration = 0, backgroundAndHandleAlphaStartDelay = 0,
741                 backgroundAndHandleAlphaDuration = 0;
742         if (duration > 0) {
743             if (animationType == TRANSITION_HANDLE_FADE) {
744                 // When fading, the handle fades in/out at the beginning of the transition with
745                 // TASKBAR_STASH_ALPHA_DURATION.
746                 backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
747                 // The iconAlphaDuration must be set to duration for the skippable interpolators
748                 // below to work.
749                 iconAlphaDuration = duration;
750             } else {
751                 iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
752                 iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
753                 backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
754 
755                 if (isStashed) {
756                     if (animationType == TRANSITION_HOME_TO_APP) {
757                         iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY;
758                     }
759                     backgroundAndHandleAlphaStartDelay = iconAlphaStartDelay;
760                     backgroundAndHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
761                 }
762 
763             }
764         }
765 
766         play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
767                 backgroundAndHandleAlphaStartDelay,
768                 backgroundAndHandleAlphaDuration, LINEAR);
769 
770         if (enableScalingRevealHomeAnimation() && !isStashed) {
771             play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
772                     0, 0, LINEAR);
773             as.addListener(AnimatorListeners.forEndCallback(
774                     () -> mTaskbarBackgroundAlphaForStash.setValue(backgroundAlphaTarget)));
775         } else {
776             play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
777                     backgroundAndHandleAlphaStartDelay,
778                     backgroundAndHandleAlphaDuration, LINEAR);
779         }
780 
781         // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
782         AnimatorSet skippable = as;
783         if (animationType == TRANSITION_HANDLE_FADE) {
784             skippable = new AnimatorSet();
785             as.play(skippable);
786             skippable.setInterpolator(isStashed ? INSTANT : FINAL_FRAME);
787         }
788 
789         final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL;
790         if (animateBg) {
791             play(skippable, mTaskbarBackgroundOffset.animateToValue(backgroundOffsetTarget), 0,
792                     duration, EMPHASIZED);
793         } else {
794             skippable.addListener(AnimatorListeners.forEndCallback(
795                     () -> mTaskbarBackgroundOffset.updateValue(backgroundOffsetTarget)));
796         }
797 
798         play(skippable, mIconAlphaForStash.animateToValue(iconAlphaTarget), iconAlphaStartDelay,
799                 iconAlphaDuration,
800                 LINEAR);
801 
802         if (isStashed) {
803             play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(),
804                     0, duration, LINEAR);
805         } else {
806             play(skippable, mControllers.taskbarSpringOnStashController.createResetAnimForUnstash(),
807                     0, duration, LINEAR);
808         }
809 
810         mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration,
811                 EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL);
812 
813         play(skippable, mControllers.stashedHandleViewController
814                 .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
815 
816         // Return the stashed handle to its default scale in case it was changed as part of the
817         // feedforward hint. Note that the reveal animation above also visually scales it.
818         skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
819                 .setDuration(isStashed ? duration / 2 : duration));
820     }
821 
getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration)822     private Animator getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration) {
823         ValueAnimator a = ValueAnimator.ofFloat(0, 1);
824         a.setDuration(duration);
825         a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
826             // This value is arbitrary.
827             private static final float ANIMATED_FRACTION_THRESHOLD = 0.25f;
828             private boolean mTaskbarBgAlphaAnimationStarted = false;
829             @Override
830             public void onAnimationUpdate(ValueAnimator valueAnimator) {
831                 if (mIsGoingHome) {
832                     mTaskbarBgAlphaAnimationStarted = true;
833                 }
834                 if (mTaskbarBgAlphaAnimationStarted) {
835                     return;
836                 }
837 
838                 if (valueAnimator.getAnimatedFraction() >= ANIMATED_FRACTION_THRESHOLD) {
839                     if (!mIsGoingHome) {
840                         playTaskbarBackgroundAlphaAnimation();
841                         setUserIsGoingHome(false);
842                         mTaskbarBgAlphaAnimationStarted = true;
843                     }
844                 }
845             }
846         });
847         return a;
848     }
849 
850     /**
851      * Sets whether the user is going home based on the current gesture.
852      */
setUserIsGoingHome(boolean isGoingHome)853     public void setUserIsGoingHome(boolean isGoingHome) {
854         mIsGoingHome = isGoingHome;
855     }
856 
857     /**
858      * Plays the taskbar background alpha animation if one is not currently playing.
859      */
playTaskbarBackgroundAlphaAnimation()860     public void playTaskbarBackgroundAlphaAnimation() {
861         if (mTaskbarBackgroundAlphaAnimator != null
862                 && mTaskbarBackgroundAlphaAnimator.isRunning()) {
863             return;
864         }
865         mTaskbarBackgroundAlphaAnimator = mTaskbarBackgroundAlphaForStash
866                 .animateToValue(1f)
867                 .setDuration(mTaskbarBackgroundDuration);
868         mTaskbarBackgroundAlphaAnimator.setInterpolator(LINEAR);
869         mTaskbarBackgroundAlphaAnimator.addListener(new AnimatorListenerAdapter() {
870             @Override
871             public void onAnimationEnd(Animator animation) {
872                 mTaskbarBackgroundAlphaAnimator = null;
873             }
874         });
875         mTaskbarBackgroundAlphaAnimator.start();
876     }
877 
play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration, Interpolator interpolator)878     private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
879             Interpolator interpolator) {
880         if (a == null) {
881             return;
882         }
883         a.setDuration(duration);
884         a.setStartDelay(startDelay);
885         a.setInterpolator(interpolator);
886         as.play(a);
887     }
888 
addJankMonitorListener( AnimatorSet animator, boolean expanding, String tag)889     private void addJankMonitorListener(
890             AnimatorSet animator, boolean expanding, String tag) {
891         View v = mControllers.taskbarActivityContext.getDragLayer();
892         if (!v.isAttachedToWindow()) {
893             // If the task bar drag layer is not attached to window, we don't need to monitor jank
894             // (actually we can't pass in an unattached view either).
895             return;
896         }
897         int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
898                 InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
899         animator.addListener(new AnimatorListenerAdapter() {
900             @Override
901             public void onAnimationStart(@NonNull Animator animation) {
902                 final Configuration.Builder builder =
903                         Configuration.Builder.withView(action, v);
904                 if (tag != null) {
905                     builder.setTag(tag);
906                 }
907                 InteractionJankMonitor.getInstance().begin(builder);
908             }
909 
910             @Override
911             public void onAnimationEnd(@NonNull Animator animation) {
912                 InteractionJankMonitor.getInstance().end(action);
913             }
914         });
915     }
916 
917     /**
918      * Creates and starts a partial unstash animation, hinting at the new state that will trigger
919      * when long press is detected.
920      *
921      * @param animateForward Whether we are going towards the new unstashed state or returning to
922      *                       the stashed state.
923      */
startUnstashHint(boolean animateForward)924     protected void startUnstashHint(boolean animateForward) {
925         if (!isStashed()) {
926             // Already unstashed, no need to hint in that direction.
927             return;
928         }
929         mTaskbarStashedHandleHintScale.animateToValue(
930                         animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
931                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
932     }
933 
onIsStashedChanged(boolean isStashed)934     private void onIsStashedChanged(boolean isStashed) {
935         mControllers.runAfterInit(() -> {
936             mControllers.stashedHandleViewController.onIsStashedChanged(
937                     isStashed && supportsVisualStashing());
938             mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
939         });
940     }
941 
applyState()942     public void applyState() {
943         applyState(hasAnyFlag(FLAG_IN_SETUP) ? 0 : TASKBAR_STASH_DURATION);
944     }
945 
applyState(long duration)946     public void applyState(long duration) {
947         Animator animator = createApplyStateAnimator(duration);
948         if (animator != null) {
949             animator.start();
950         }
951     }
952 
applyState(long duration, long startDelay)953     public void applyState(long duration, long startDelay) {
954         Animator animator = createApplyStateAnimator(duration);
955         if (animator != null) {
956             animator.setStartDelay(startDelay);
957             animator.start();
958         }
959     }
960 
961     /**
962      * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
963      * otherwise.
964      */
965     @Nullable
createApplyStateAnimator(long duration)966     public Animator createApplyStateAnimator(long duration) {
967         return mStatePropertyHolder.createSetStateAnimator(mState, duration);
968     }
969 
970     /**
971      * Should be called when a system gesture starts and settles, so we can remove
972      * FLAG_STASHED_IN_APP_IME while the gesture is in progress.
973      */
setSystemGestureInProgress(boolean inProgress)974     public void setSystemGestureInProgress(boolean inProgress) {
975         mIsSystemGestureInProgress = inProgress;
976         setStashedImeState();
977     }
978 
setStashedImeState()979     private void setStashedImeState() {
980         boolean shouldStashForIme = shouldStashForIme();
981         if (hasAnyFlag(FLAG_STASHED_IME) != shouldStashForIme) {
982             updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme);
983             applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
984         } else {
985             applyState(mControllers.taskbarOverlayController.getCloseDuration());
986         }
987     }
988 
989     /**
990      * Should be called when Ime inset is changed to determine if taskbar should be stashed
991      */
onImeInsetChanged()992     public void onImeInsetChanged() {
993         setStashedImeState();
994     }
995 
996     /**
997      * When hiding the IME, delay the unstash animation to align with the end of the transition.
998      */
getTaskbarStashStartDelayForIme()999     private long getTaskbarStashStartDelayForIme() {
1000         if (mIsImeShowing) {
1001             // Only delay when IME is exiting, not entering.
1002             return 0;
1003         }
1004         // This duration is based on input_method_extract_exit.xml.
1005         long imeExitDuration = mControllers.taskbarActivityContext.getResources()
1006                 .getInteger(android.R.integer.config_shortAnimTime);
1007         return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
1008     }
1009 
1010     /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim)1011     public void updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim) {
1012         long animDuration = TASKBAR_STASH_DURATION;
1013         long startDelay = 0;
1014 
1015         updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
1016                 SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
1017 
1018         boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW)
1019                 && hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED)
1020                 && DisplayController.isTransientTaskbar(mActivity);
1021         updateStateForFlag(FLAG_STASHED_SYSUI,
1022                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
1023         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
1024                 SystemUiFlagUtils.isLocked(systemUiStateFlags));
1025 
1026         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
1027         mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
1028         if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
1029             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
1030             startDelay = getTaskbarStashStartDelayForIme();
1031         }
1032 
1033         applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
1034     }
1035 
1036     /**
1037      * We stash when IME or IME switcher is showing.
1038      *
1039      * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
1040      * <p>Do not stash if taskbar is transient.
1041      * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked.
1042      * <p>Do not stash if a system gesture is started.
1043      */
shouldStashForIme()1044     private boolean shouldStashForIme() {
1045         if (DisplayController.isTransientTaskbar(mActivity)) {
1046             return false;
1047         }
1048         // Do not stash if in small screen, with 3 button nav, and in landscape.
1049         if (mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
1050                 && mActivity.getDeviceProfile().isLandscape) {
1051             return false;
1052         }
1053 
1054         // Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked
1055         if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
1056                 && !mActivity.isImeDocked()) {
1057             return false;
1058         }
1059 
1060         // Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
1061         DesktopVisibilityController visibilityController =
1062                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
1063         if (visibilityController != null && mActivity.isHardwareKeyboard()
1064                 && mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) {
1065             return false;
1066         }
1067 
1068         // Do not stash if a gesture started.
1069         if (mIsSystemGestureInProgress) {
1070             return false;
1071         }
1072 
1073         return mIsImeShowing || mIsImeSwitcherShowing;
1074     }
1075 
1076     /**
1077      * Updates the proper flag to indicate whether the task bar should be stashed.
1078      *
1079      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
1080      *
1081      * @param flag    The flag to update.
1082      * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
1083      *                unstashed.
1084      * @return Whether the flag state changed.
1085      */
updateStateForFlag(long flag, boolean enabled)1086     public boolean updateStateForFlag(long flag, boolean enabled) {
1087         long oldState = mState;
1088         if (enabled) {
1089             mState |= flag;
1090         } else {
1091             mState &= ~flag;
1092         }
1093         return mState != oldState;
1094     }
1095 
1096     /**
1097      * Called after updateStateForFlag() and applyState() have been called.
1098      *
1099      * @param changedFlags The flags that have changed.
1100      */
onStateChangeApplied(long changedFlags)1101     private void onStateChangeApplied(long changedFlags) {
1102         if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
1103             mControllers.uiController.onStashedInAppChanged();
1104         }
1105         if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAGS_IN_APP)) {
1106             notifyStashChange(/* visible */ hasAnyFlag(FLAGS_IN_APP),
1107                     /* stashed */ isStashedInApp());
1108             mControllers.taskbarAutohideSuspendController.updateFlag(
1109                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp());
1110         }
1111         if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
1112             mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)
1113                     ? LAUNCHER_TRANSIENT_TASKBAR_HIDE
1114                     : LAUNCHER_TRANSIENT_TASKBAR_SHOW);
1115             mControllers.taskbarAutohideSuspendController.updateFlag(
1116                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
1117                     !hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
1118         }
1119         mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
1120     }
1121 
notifyStashChange(boolean visible, boolean stashed)1122     private void notifyStashChange(boolean visible, boolean stashed) {
1123         mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
1124         setUpTaskbarSystemAction(visible);
1125         mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
1126     }
1127 
1128     /**
1129      * Setup system action for showing Taskbar depending on its visibility.
1130      */
setUpTaskbarSystemAction(boolean visible)1131     public void setUpTaskbarSystemAction(boolean visible) {
1132         UI_HELPER_EXECUTOR.execute(() -> {
1133             if (!visible || !DisplayController.isTransientTaskbar(mActivity)) {
1134                 mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR);
1135                 mIsTaskbarSystemActionRegistered = false;
1136                 return;
1137             }
1138 
1139             if (!mIsTaskbarSystemActionRegistered) {
1140                 RemoteAction taskbarRemoteAction = new RemoteAction(
1141                         Icon.createWithResource(mActivity, R.drawable.ic_info_no_shadow),
1142                         mActivity.getString(R.string.taskbar_a11y_title),
1143                         mActivity.getString(R.string.taskbar_a11y_title),
1144                         mTaskbarSharedState.taskbarSystemActionPendingIntent);
1145 
1146                 mAccessibilityManager.registerSystemAction(taskbarRemoteAction,
1147                         SYSTEM_ACTION_ID_TASKBAR);
1148                 mIsTaskbarSystemActionRegistered = true;
1149             }
1150         });
1151     }
1152 
1153     /**
1154      * Clean up on destroy from TaskbarControllers
1155      */
onDestroy()1156     public void onDestroy() {
1157         UI_HELPER_EXECUTOR.execute(
1158                 () -> mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR));
1159     }
1160 
1161     /**
1162      * Cancels a timeout if any exists.
1163      */
cancelTimeoutIfExists()1164     public void cancelTimeoutIfExists() {
1165         if (mTimeoutAlarm.alarmPending()) {
1166             mTimeoutAlarm.cancelAlarm();
1167         }
1168     }
1169 
1170     /**
1171      * Updates the status of the taskbar timeout.
1172      *
1173      * @param isAutohideSuspended If true, cancels any existing timeout
1174      *                            If false, attempts to re/start the timeout
1175      */
updateTaskbarTimeout(boolean isAutohideSuspended)1176     public void updateTaskbarTimeout(boolean isAutohideSuspended) {
1177         if (!DisplayController.isTransientTaskbar(mActivity)) {
1178             return;
1179         }
1180         if (isAutohideSuspended) {
1181             cancelTimeoutIfExists();
1182         } else {
1183             tryStartTaskbarTimeout();
1184         }
1185     }
1186 
1187     /**
1188      * Attempts to start timer to auto hide the taskbar based on time.
1189      */
tryStartTaskbarTimeout()1190     public void tryStartTaskbarTimeout() {
1191         if (!DisplayController.isTransientTaskbar(mActivity)
1192                 || mIsStashed
1193                 || mEnableBlockingTimeoutDuringTests) {
1194             return;
1195         }
1196 
1197         cancelTimeoutIfExists();
1198 
1199         mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout);
1200         mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout());
1201     }
1202 
1203     /**
1204      * returns appropriate timeout for taskbar to stash depending on accessibility being on/off.
1205      */
getTaskbarAutoHideTimeout()1206     private long getTaskbarAutoHideTimeout() {
1207         return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS,
1208                 FLAG_CONTENT_CONTROLS);
1209     }
1210 
onTaskbarTimeout(Alarm alarm)1211     private void onTaskbarTimeout(Alarm alarm) {
1212         if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) {
1213             return;
1214         }
1215         updateAndAnimateTransientTaskbarForTimeout();
1216     }
1217 
1218     @Override
dumpLogs(String prefix, PrintWriter pw)1219     public void dumpLogs(String prefix, PrintWriter pw) {
1220         pw.println(prefix + "TaskbarStashController:");
1221 
1222         pw.println(prefix + "\tmStashedHeight=" + mStashedHeight);
1223         pw.println(prefix + "\tmUnstashedHeight=" + mUnstashedHeight);
1224         pw.println(prefix + "\tmIsStashed=" + mIsStashed);
1225         pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
1226         pw.println(prefix + "\tmState=" + getStateString(mState));
1227         pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
1228         pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
1229         pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
1230     }
1231 
getStateString(long flags)1232     private static String getStateString(long flags) {
1233         StringJoiner sj = new StringJoiner("|");
1234         appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP");
1235         appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI");
1236         appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP");
1237         appendFlag(sj, flags, FLAG_STASHED_IME, "FLAG_STASHED_IN_APP_IME");
1238         appendFlag(sj, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE");
1239         appendFlag(sj, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_TASKBAR_ALL_APPS");
1240         appendFlag(sj, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP");
1241         appendFlag(sj, flags, FLAG_STASHED_IN_APP_AUTO, "FLAG_STASHED_IN_APP_AUTO");
1242         appendFlag(sj, flags, FLAG_STASHED_SYSUI, "FLAG_STASHED_SYSUI");
1243         appendFlag(sj, flags, FLAG_STASHED_DEVICE_LOCKED, "FLAG_STASHED_DEVICE_LOCKED");
1244         appendFlag(sj, flags, FLAG_IN_OVERVIEW, "FLAG_IN_OVERVIEW");
1245         return sj.toString();
1246     }
1247 
1248     private class StatePropertyHolder {
1249         private final LongPredicate mStashCondition;
1250 
1251         private boolean mIsStashed;
1252         private @StashAnimation int mLastStartedTransitionType = TRANSITION_DEFAULT;
1253         private long mPrevFlags;
1254 
1255         private long mLastUnlockTransitionTimeout = 0;
1256 
StatePropertyHolder(LongPredicate stashCondition)1257         StatePropertyHolder(LongPredicate stashCondition) {
1258             mStashCondition = stashCondition;
1259         }
1260 
1261         /**
1262          * Creates an animator (stored in mAnimator) which applies the latest state, potentially
1263          * creating a new animation (stored in mAnimator).
1264          *
1265          * @param flags    The latest flags to apply (see the top of this file).
1266          * @param duration The length of the animation.
1267          * @return mAnimator if mIsStashed changed, or {@code null} otherwise.
1268          */
1269         @Nullable
createSetStateAnimator(long flags, long duration)1270         public Animator createSetStateAnimator(long flags, long duration) {
1271             boolean isStashed = mStashCondition.test(flags);
1272 
1273             if (DEBUG) {
1274                 String stateString = formatFlagChange(flags, mPrevFlags,
1275                         TaskbarStashController::getStateString);
1276                 Log.d(TAG, "createSetStateAnimator: flags: " + stateString
1277                         + ", duration: " + duration
1278                         + ", isStashed: " + isStashed
1279                         + ", mIsStashed: " + mIsStashed);
1280             }
1281 
1282             long changedFlags = mPrevFlags ^ flags;
1283             if (mPrevFlags != flags) {
1284                 onStateChangeApplied(changedFlags);
1285                 mPrevFlags = flags;
1286             }
1287 
1288             boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED)
1289                     && !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
1290             if (isUnlockTransition) {
1291                 // the launcher might not be resumed at the time the device is considered
1292                 // unlocked (when the keyguard goes away), but possibly shortly afterwards.
1293                 // To play the unlock transition at the time the unstash animation actually happens,
1294                 // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
1295                 mLastUnlockTransitionTimeout =
1296                         SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
1297             }
1298 
1299             @StashAnimation int animationType = computeTransitionType(changedFlags);
1300 
1301             // Allow re-starting animation if upgrading from default animation type, otherwise
1302             // stick with the already started transition.
1303             boolean transitionTypeChanged = mAnimator != null && mAnimator.isStarted()
1304                     && mLastStartedTransitionType == TRANSITION_DEFAULT
1305                     && animationType != TRANSITION_DEFAULT;
1306 
1307             // It is possible for stash=false to be requested by TRANSITION_HOME_TO_APP and
1308             // TRANSITION_DEFAULT in quick succession. In this case, we should ignore
1309             // transitionTypeChanged because the animations are exactly the same.
1310             if (transitionTypeChanged
1311                     && (!mIsStashed && !isStashed)
1312                     && animationType == TRANSITION_HOME_TO_APP) {
1313                 transitionTypeChanged = false;
1314             }
1315 
1316             if (mIsStashed != isStashed || transitionTypeChanged) {
1317                 mIsStashed = isStashed;
1318                 mLastStartedTransitionType = animationType;
1319 
1320                 // This sets mAnimator.
1321                 createAnimToIsStashed(mIsStashed, duration, animationType,
1322                         computeTaskbarJankMonitorTag(changedFlags));
1323                 return mAnimator;
1324             }
1325             return null;
1326         }
1327 
1328         /** Calculates the tag for CUJ_TASKBAR_EXPAND and CUJ_TASKBAR_COLLAPSE jank traces. */
computeTaskbarJankMonitorTag(long changedFlags)1329         private String computeTaskbarJankMonitorTag(long changedFlags) {
1330             if (hasAnyFlag(changedFlags, FLAG_IN_APP)) {
1331                 // moving in or out of the app
1332                 if (hasAnyFlag(FLAG_IN_APP)) {
1333                     return "Home to App";
1334                 } else {
1335                     return "App to Home";
1336                 }
1337             }
1338             if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) {
1339                 // stash and unstash with-in the app
1340                 if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)) {
1341                     return "Stashed in app";
1342                 } else {
1343                     return "Manually unstashed";
1344                 }
1345             }
1346             return "";
1347         }
1348 
computeTransitionType(long changedFlags)1349         private @StashAnimation int computeTransitionType(long changedFlags) {
1350 
1351             boolean hotseatHiddenDuringAppLaunch =
1352                     !mControllers.uiController.isHotseatIconOnTopWhenAligned()
1353                             && hasAnyFlag(changedFlags, FLAG_IN_APP);
1354             if (hotseatHiddenDuringAppLaunch) {
1355                 // When launching an app from the all-apps drawer, the hotseat is hidden behind the
1356                 // drawer. In this case, the navbar must just fade in, without a stash transition,
1357                 // as the taskbar stash animation would otherwise be visible above the all-apps
1358                 // drawer once the hotseat is detached.
1359                 return TRANSITION_HANDLE_FADE;
1360             }
1361 
1362             boolean isUnlockTransition =
1363                     SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
1364             if (isUnlockTransition) {
1365                 // When transitioning to unlocked device, the  hotseat will already be visible on
1366                 // the homescreen, thus do not play an un-stash animation.
1367                 // Keep isUnlockTransition in sync with its counterpart in
1368                 // TaskbarLauncherStateController#onStateChangeApplied.
1369                 return TRANSITION_HANDLE_FADE;
1370             }
1371 
1372             boolean homeToApp = hasAnyFlag(changedFlags, FLAG_IN_APP) && hasAnyFlag(FLAG_IN_APP);
1373             if (homeToApp) {
1374                 return TRANSITION_HOME_TO_APP;
1375             }
1376 
1377             return TRANSITION_DEFAULT;
1378         }
1379     }
1380 }
1381