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