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 
20 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
24 
25 import android.animation.Animator;
26 import android.animation.AnimatorListenerAdapter;
27 import android.animation.AnimatorSet;
28 import android.annotation.Nullable;
29 import android.content.SharedPreferences;
30 import android.content.res.Resources;
31 import android.view.ViewConfiguration;
32 
33 import com.android.launcher3.R;
34 import com.android.launcher3.Utilities;
35 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
36 import com.android.quickstep.AnimatedFloat;
37 import com.android.quickstep.SystemUiProxy;
38 
39 import java.util.function.IntPredicate;
40 
41 /**
42  * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to
43  * create a cohesive animation between stashed/unstashed states.
44  */
45 public class TaskbarStashController {
46 
47     public static final int FLAG_IN_APP = 1 << 0;
48     public static final int FLAG_STASHED_IN_APP_MANUAL = 1 << 1; // long press, persisted
49     public static final int FLAG_STASHED_IN_APP_PINNED = 1 << 2; // app pinning
50     public static final int FLAG_STASHED_IN_APP_EMPTY = 1 << 3; // no hotseat icons
51     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
52     public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
53     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
54 
55     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
56     private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
57             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
58             | FLAG_STASHED_IN_APP_IME;
59 
60     // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
61     // height. This way the reported insets are consistent even during transitions out of the app.
62     // Currently any flag that causes us to stash in an app is included, except for IME since that
63     // covers the underlying app anyway and thus the app shouldn't change insets.
64     private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP
65             & ~FLAG_STASHED_IN_APP_IME;
66 
67     /**
68      * How long to stash/unstash when manually invoked via long press.
69      */
70     public static final long TASKBAR_STASH_DURATION = 300;
71 
72     /**
73      * How long to stash/unstash when keyboard is appearing/disappearing.
74      */
75     private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
76 
77     /**
78      * The scale TaskbarView animates to when being stashed.
79      */
80     private static final float STASHED_TASKBAR_SCALE = 0.5f;
81 
82     /**
83      * How long the hint animation plays, starting on motion down.
84      */
85     private static final long TASKBAR_HINT_STASH_DURATION =
86             ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
87 
88     /**
89      * The scale that TaskbarView animates to when hinting towards the stashed state.
90      */
91     private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
92 
93     /**
94      * The scale that the stashed handle animates to when hinting towards the unstashed state.
95      */
96     private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f;
97 
98     /**
99      * The SharedPreferences key for whether user has manually stashed the taskbar.
100      */
101     private static final String SHARED_PREFS_STASHED_KEY = "taskbar_is_stashed";
102 
103     /**
104      * Whether taskbar should be stashed out of the box.
105      */
106     private static final boolean DEFAULT_STASHED_PREF = false;
107 
108     private final TaskbarActivityContext mActivity;
109     private final SharedPreferences mPrefs;
110     private final int mStashedHeight;
111     private final int mUnstashedHeight;
112     private final SystemUiProxy mSystemUiProxy;
113 
114     // Initialized in init.
115     private TaskbarControllers mControllers;
116     // Taskbar background properties.
117     private AnimatedFloat mTaskbarBackgroundOffset;
118     private AnimatedFloat mTaskbarImeBgAlpha;
119     // TaskbarView icon properties.
120     private AlphaProperty mIconAlphaForStash;
121     private AnimatedFloat mIconScaleForStash;
122     private AnimatedFloat mIconTranslationYForStash;
123     // Stashed handle properties.
124     private AlphaProperty mTaskbarStashedHandleAlpha;
125     private AnimatedFloat mTaskbarStashedHandleHintScale;
126 
127     /** Whether we are currently visually stashed (might change based on launcher state). */
128     private boolean mIsStashed = false;
129     private int mState;
130 
131     private @Nullable AnimatorSet mAnimator;
132     private boolean mIsSystemGestureInProgress;
133     private boolean mIsImeShowing;
134 
135     // Evaluate whether the handle should be stashed
136     private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder(
137             flags -> {
138                 boolean inApp = hasAnyFlag(flags, FLAG_IN_APP);
139                 boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP);
140                 boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE);
141                 return (inApp && stashedInApp) || (!inApp && stashedLauncherState);
142             });
143 
TaskbarStashController(TaskbarActivityContext activity)144     public TaskbarStashController(TaskbarActivityContext activity) {
145         mActivity = activity;
146         mPrefs = Utilities.getPrefs(mActivity);
147         final Resources resources = mActivity.getResources();
148         mStashedHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
149         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
150         mUnstashedHeight = mActivity.getDeviceProfile().taskbarSize;
151     }
152 
init(TaskbarControllers controllers, TaskbarSharedState sharedState)153     public void init(TaskbarControllers controllers, TaskbarSharedState sharedState) {
154         mControllers = controllers;
155 
156         TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
157         mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
158         mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
159 
160         TaskbarViewController taskbarViewController = controllers.taskbarViewController;
161         mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().getProperty(
162                 TaskbarViewController.ALPHA_INDEX_STASH);
163         mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash();
164         mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash();
165 
166         StashedHandleViewController stashedHandleController =
167                 controllers.stashedHandleViewController;
168         mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().getProperty(
169                 StashedHandleViewController.ALPHA_INDEX_STASHED);
170         mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
171 
172         boolean isManuallyStashedInApp = supportsManualStashing()
173                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
174         boolean isInSetup = !mActivity.isUserSetupComplete() || sharedState.setupUIVisible;
175         updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
176         // TODO(b/204384193): Temporarily disable SUW specific logic
177         // updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup);
178         if (isInSetup) {
179             // Update the in-app state to ensure isStashed() reflects right state during SUW
180             updateStateForFlag(FLAG_IN_APP, true);
181         }
182         applyState();
183 
184         notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp());
185     }
186 
187     /**
188      * Returns whether the taskbar can visually stash into a handle based on the current device
189      * state.
190      */
supportsVisualStashing()191     private boolean supportsVisualStashing() {
192         return !mActivity.isThreeButtonNav();
193     }
194 
195     /**
196      * Returns whether the user can manually stash the taskbar based on the current device state.
197      */
supportsManualStashing()198     private boolean supportsManualStashing() {
199         return supportsVisualStashing()
200                 && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || supportsStashingForTests());
201     }
202 
supportsStashingForTests()203     private boolean supportsStashingForTests() {
204         // TODO: enable this for tests that specifically check stash/unstash behavior.
205         return false;
206     }
207 
208     /**
209      * Sets the flag indicating setup UI is visible
210      */
setSetupUIVisible(boolean isVisible)211     protected void setSetupUIVisible(boolean isVisible) {
212         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP,
213                 isVisible || !mActivity.isUserSetupComplete());
214         applyState();
215     }
216 
217     /**
218      * Returns whether the taskbar is currently visually stashed.
219      */
isStashed()220     public boolean isStashed() {
221         return mIsStashed;
222     }
223 
224     /**
225      * Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
226      */
isStashedInApp()227     public boolean isStashedInApp() {
228         return hasAnyFlag(FLAGS_STASHED_IN_APP);
229     }
230 
231     /**
232      * Returns whether the taskbar should be stashed in the current LauncherState.
233      */
isInStashedLauncherState()234     public boolean isInStashedLauncherState() {
235         return hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing();
236     }
237 
hasAnyFlag(int flagMask)238     private boolean hasAnyFlag(int flagMask) {
239         return hasAnyFlag(mState, flagMask);
240     }
241 
hasAnyFlag(int flags, int flagMask)242     private boolean hasAnyFlag(int flags, int flagMask) {
243         return (flags & flagMask) != 0;
244     }
245 
246 
247     /**
248      * Returns whether the taskbar is currently visible and in an app.
249      */
isInAppAndNotStashed()250     public boolean isInAppAndNotStashed() {
251         return !mIsStashed && (mState & FLAG_IN_APP) != 0;
252     }
253 
254     /**
255      * Returns the height that taskbar will inset when inside apps.
256      */
getContentHeightToReportToApps()257     public int getContentHeightToReportToApps() {
258         if (hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) {
259             boolean isAnimating = mAnimator != null && mAnimator.isStarted();
260             return mControllers.stashedHandleViewController.isStashedHandleVisible() || isAnimating
261                     ? mStashedHeight : 0;
262         }
263         return mUnstashedHeight;
264     }
265 
getStashedHeight()266     public int getStashedHeight() {
267         return mStashedHeight;
268     }
269 
270     /**
271      * Should be called when long pressing the nav region when taskbar is present.
272      * @return Whether taskbar was stashed and now is unstashed.
273      */
onLongPressToUnstashTaskbar()274     public boolean onLongPressToUnstashTaskbar() {
275         if (!isStashed()) {
276             // We only listen for long press on the nav region to unstash the taskbar. To stash the
277             // taskbar, we use an OnLongClickListener on TaskbarView instead.
278             return false;
279         }
280         if (updateAndAnimateIsManuallyStashedInApp(false)) {
281             mControllers.taskbarActivityContext.getDragLayer().performHapticFeedback(LONG_PRESS);
282             return true;
283         }
284         return false;
285     }
286 
287     /**
288      * Updates whether we should stash the taskbar when in apps, and animates to the changed state.
289      * @return Whether we started an animation to either be newly stashed or unstashed.
290      */
updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp)291     public boolean updateAndAnimateIsManuallyStashedInApp(boolean isManuallyStashedInApp) {
292         if (!supportsManualStashing()) {
293             return false;
294         }
295         if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL) != isManuallyStashedInApp) {
296             mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_KEY, isManuallyStashedInApp).apply();
297             updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
298             applyState();
299             return true;
300         }
301         return false;
302     }
303 
304     /**
305      * Create a stash animation and save to {@link #mAnimator}.
306      * @param isStashed whether it's a stash animation or an unstash animation
307      * @param duration duration of the animation
308      * @param startDelay how many milliseconds to delay the animation after starting it.
309      */
createAnimToIsStashed(boolean isStashed, long duration, long startDelay)310     private void createAnimToIsStashed(boolean isStashed, long duration, long startDelay) {
311         if (mAnimator != null) {
312             mAnimator.cancel();
313         }
314         mAnimator = new AnimatorSet();
315 
316         if (!supportsVisualStashing()) {
317             // Just hide/show the icons and background instead of stashing into a handle.
318             mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1)
319                     .setDuration(duration));
320             mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
321                     hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
322             mAnimator.setStartDelay(startDelay);
323             mAnimator.addListener(new AnimatorListenerAdapter() {
324                 @Override
325                 public void onAnimationEnd(Animator animation) {
326                     mAnimator = null;
327                 }
328             });
329             return;
330         }
331 
332         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
333         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
334         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
335         AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
336 
337         final float firstHalfDurationScale;
338         final float secondHalfDurationScale;
339 
340         if (isStashed) {
341             firstHalfDurationScale = 0.75f;
342             secondHalfDurationScale = 0.5f;
343             final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
344 
345             fullLengthAnimatorSet.playTogether(
346                     mTaskbarBackgroundOffset.animateToValue(1),
347                     mIconTranslationYForStash.animateToValue(stashTranslation)
348             );
349             firstHalfAnimatorSet.playTogether(
350                     mIconAlphaForStash.animateToValue(0),
351                     mIconScaleForStash.animateToValue(STASHED_TASKBAR_SCALE)
352             );
353             secondHalfAnimatorSet.playTogether(
354                     mTaskbarStashedHandleAlpha.animateToValue(1)
355             );
356         } else  {
357             firstHalfDurationScale = 0.5f;
358             secondHalfDurationScale = 0.75f;
359 
360             fullLengthAnimatorSet.playTogether(
361                     mTaskbarBackgroundOffset.animateToValue(0),
362                     mIconScaleForStash.animateToValue(1),
363                     mIconTranslationYForStash.animateToValue(0)
364             );
365             firstHalfAnimatorSet.playTogether(
366                     mTaskbarStashedHandleAlpha.animateToValue(0)
367             );
368             secondHalfAnimatorSet.playTogether(
369                     mIconAlphaForStash.animateToValue(1)
370             );
371         }
372 
373         fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
374                 .createRevealAnimToIsStashed(isStashed));
375         // Return the stashed handle to its default scale in case it was changed as part of the
376         // feedforward hint. Note that the reveal animation above also visually scales it.
377         fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f));
378 
379         fullLengthAnimatorSet.setDuration(duration);
380         firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
381         secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
382         secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
383 
384         mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
385                 secondHalfAnimatorSet);
386         mAnimator.setStartDelay(startDelay);
387         mAnimator.addListener(new AnimatorListenerAdapter() {
388             @Override
389             public void onAnimationStart(Animator animation) {
390                 mIsStashed = isStashed;
391                 onIsStashed(mIsStashed);
392             }
393 
394             @Override
395             public void onAnimationEnd(Animator animation) {
396                 mAnimator = null;
397             }
398         });
399     }
400 
401     /**
402      * Creates and starts a partial stash animation, hinting at the new state that will trigger when
403      * long press is detected.
404      * @param animateForward Whether we are going towards the new stashed state or returning to the
405      *                       unstashed state.
406      */
startStashHint(boolean animateForward)407     public void startStashHint(boolean animateForward) {
408         if (isStashed() || !supportsManualStashing()) {
409             // Already stashed, no need to hint in that direction.
410             return;
411         }
412         mIconScaleForStash.animateToValue(
413                 animateForward ? STASHED_TASKBAR_HINT_SCALE : 1)
414                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
415     }
416 
417     /**
418      * Creates and starts a partial unstash animation, hinting at the new state that will trigger
419      * when long press is detected.
420      * @param animateForward Whether we are going towards the new unstashed state or returning to
421      *                       the stashed state.
422      */
startUnstashHint(boolean animateForward)423     public void startUnstashHint(boolean animateForward) {
424         if (!isStashed()) {
425             // Already unstashed, no need to hint in that direction.
426             return;
427         }
428         mTaskbarStashedHandleHintScale.animateToValue(
429                 animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1)
430                 .setDuration(TASKBAR_HINT_STASH_DURATION).start();
431     }
432 
onIsStashed(boolean isStashed)433     private void onIsStashed(boolean isStashed) {
434         mControllers.stashedHandleViewController.onIsStashed(isStashed);
435     }
436 
applyState()437     public void applyState() {
438         applyState(TASKBAR_STASH_DURATION);
439     }
440 
applyState(long duration)441     public void applyState(long duration) {
442         mStatePropertyHolder.setState(mState, duration, true);
443     }
444 
applyState(long duration, long startDelay)445     public void applyState(long duration, long startDelay) {
446         mStatePropertyHolder.setState(mState, duration, startDelay, true);
447     }
448 
applyStateWithoutStart()449     public Animator applyStateWithoutStart() {
450         return applyStateWithoutStart(TASKBAR_STASH_DURATION);
451     }
452 
applyStateWithoutStart(long duration)453     public Animator applyStateWithoutStart(long duration) {
454         return mStatePropertyHolder.setState(mState, duration, false);
455     }
456 
457     /**
458      * Should be called when a system gesture starts and settles, so we can defer updating
459      * FLAG_STASHED_IN_APP_IME until after the gesture transition completes.
460      */
setSystemGestureInProgress(boolean inProgress)461     public void setSystemGestureInProgress(boolean inProgress) {
462         mIsSystemGestureInProgress = inProgress;
463         // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
464         if (!mIsSystemGestureInProgress) {
465             updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
466             applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme());
467         }
468     }
469 
470     /**
471      * When hiding the IME, delay the unstash animation to align with the end of the transition.
472      */
getTaskbarStashStartDelayForIme()473     private long getTaskbarStashStartDelayForIme() {
474         if (mIsImeShowing) {
475             // Only delay when IME is exiting, not entering.
476             return 0;
477         }
478         // This duration is based on input_method_extract_exit.xml.
479         long imeExitDuration = mControllers.taskbarActivityContext.getResources()
480                 .getInteger(android.R.integer.config_shortAnimTime);
481         return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME;
482     }
483 
484     /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */
updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)485     public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
486         long animDuration = TASKBAR_STASH_DURATION;
487         long startDelay = 0;
488 
489         updateStateForFlag(FLAG_STASHED_IN_APP_PINNED,
490                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING));
491 
492         // Only update FLAG_STASHED_IN_APP_IME when system gesture is not in progress.
493         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
494         if (!mIsSystemGestureInProgress) {
495             updateStateForFlag(FLAG_STASHED_IN_APP_IME, mIsImeShowing);
496             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
497             startDelay = getTaskbarStashStartDelayForIme();
498         }
499 
500         applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
501     }
502 
503     /**
504      * Updates the proper flag to indicate whether the task bar should be stashed.
505      *
506      * Note that this only updates the flag. {@link #applyState()} needs to be called separately.
507      *
508      * @param flag The flag to update.
509      * @param enabled Whether to enable the flag: True will cause the task bar to be stashed /
510      *                unstashed.
511      */
updateStateForFlag(int flag, boolean enabled)512     public void updateStateForFlag(int flag, boolean enabled) {
513         if (enabled) {
514             mState |= flag;
515         } else {
516             mState &= ~flag;
517         }
518     }
519 
520     /**
521      * Called after updateStateForFlag() and applyState() have been called.
522      * @param changedFlags The flags that have changed.
523      */
onStateChangeApplied(int changedFlags)524     private void onStateChangeApplied(int changedFlags) {
525         if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) {
526             mControllers.uiController.onStashedInAppChanged();
527         }
528         if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAG_IN_APP)) {
529             notifyStashChange(/* visible */ hasAnyFlag(FLAG_IN_APP),
530                             /* stashed */ isStashedInApp());
531         }
532         if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_MANUAL)) {
533             if (hasAnyFlag(FLAG_STASHED_IN_APP_MANUAL)) {
534                 mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_HIDE);
535             } else {
536                 mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_LONGPRESS_SHOW);
537             }
538         }
539     }
540 
notifyStashChange(boolean visible, boolean stashed)541     private void notifyStashChange(boolean visible, boolean stashed) {
542         mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
543         mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
544     }
545 
546     private class StatePropertyHolder {
547         private final IntPredicate mStashCondition;
548 
549         private boolean mIsStashed;
550         private int mPrevFlags;
551 
StatePropertyHolder(IntPredicate stashCondition)552         StatePropertyHolder(IntPredicate stashCondition) {
553             mStashCondition = stashCondition;
554         }
555 
556         /**
557          * @see #setState(int, long, long, boolean) with a default startDelay = 0.
558          */
setState(int flags, long duration, boolean start)559         public Animator setState(int flags, long duration, boolean start) {
560             return setState(flags, duration, 0 /* startDelay */, start);
561         }
562 
563         /**
564          * Applies the latest state, potentially calling onStateChangeApplied() and creating a new
565          * animation (stored in mAnimator) which is started if {@param start} is true.
566          * @param flags The latest flags to apply (see the top of this file).
567          * @param duration The length of the animation.
568          * @param startDelay How long to delay the animation after calling start().
569          * @param start Whether to start mAnimator immediately.
570          * @return mAnimator if mIsStashed changed, else null.
571          */
setState(int flags, long duration, long startDelay, boolean start)572         public Animator setState(int flags, long duration, long startDelay, boolean start) {
573             int changedFlags = mPrevFlags ^ flags;
574             if (mPrevFlags != flags) {
575                 onStateChangeApplied(changedFlags);
576                 mPrevFlags = flags;
577             }
578             boolean isStashed = mStashCondition.test(flags);
579             if (mIsStashed != isStashed) {
580                 mIsStashed = isStashed;
581 
582                 // This sets mAnimator.
583                 createAnimToIsStashed(mIsStashed, duration, startDelay);
584                 if (start) {
585                     mAnimator.start();
586                 }
587                 return mAnimator;
588             }
589             return null;
590         }
591     }
592 }
593