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