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