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 com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED; 19 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; 20 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE; 21 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME; 22 import static com.android.launcher3.util.FlagDebugUtils.appendFlag; 23 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; 24 import static com.android.systemui.animation.Interpolators.EMPHASIZED; 25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; 27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK; 28 import static com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE; 29 30 import android.animation.Animator; 31 import android.animation.AnimatorListenerAdapter; 32 import android.animation.AnimatorSet; 33 import android.animation.ObjectAnimator; 34 import android.os.SystemClock; 35 import android.util.Log; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 40 import com.android.launcher3.AbstractFloatingView; 41 import com.android.launcher3.DeviceProfile; 42 import com.android.launcher3.LauncherState; 43 import com.android.launcher3.QuickstepTransitionManager; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.anim.AnimatedFloat; 46 import com.android.launcher3.anim.AnimatorListeners; 47 import com.android.launcher3.statemanager.StateManager; 48 import com.android.launcher3.uioverrides.QuickstepLauncher; 49 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; 50 import com.android.quickstep.RecentsAnimationCallbacks; 51 import com.android.quickstep.RecentsAnimationController; 52 import com.android.quickstep.views.RecentsView; 53 import com.android.systemui.animation.ViewRootSync; 54 import com.android.systemui.shared.recents.model.ThumbnailData; 55 56 import java.io.PrintWriter; 57 import java.util.HashMap; 58 import java.util.StringJoiner; 59 60 /** 61 * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate 62 * the task bar accordingly. 63 */ 64 public class TaskbarLauncherStateController { 65 66 private static final String TAG = TaskbarLauncherStateController.class.getSimpleName(); 67 private static final boolean DEBUG = false; 68 69 /** Launcher activity is resumed and focused. */ 70 public static final int FLAG_RESUMED = 1 << 0; 71 72 /** 73 * A external transition / animation is running that will result in FLAG_RESUMED being set. 74 **/ 75 public static final int FLAG_TRANSITION_TO_RESUMED = 1 << 1; 76 77 /** 78 * Set while the launcher state machine is performing a state transition, see {@link 79 * StateManager.StateListener}. 80 */ 81 public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2; 82 83 /** 84 * Whether the screen is currently on, or is transitioning to be on. 85 * 86 * This is cleared as soon as the screen begins to transition off. 87 */ 88 private static final int FLAG_AWAKE = 1 << 3; 89 90 /** 91 * Captures whether the launcher was active at the time the FLAG_AWAKE was cleared. 92 * Always cleared when FLAG_AWAKE is set. 93 * <p> 94 * FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this 95 * point. Thus, this flag indicates whether the launcher will be shown when the device wakes up 96 * again. 97 */ 98 private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4; 99 100 /** 101 * Whether the device is currently locked. 102 * <ul> 103 * <li>While locked, the taskbar is always stashed.<li/> 104 * <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li> 105 * </ul> 106 */ 107 private static final int FLAG_DEVICE_LOCKED = 1 << 5; 108 109 /** 110 * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed). 111 * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by 112 * hiding the inset. 113 * 114 * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible 115 * resting state while hidden is stashed. 116 */ 117 private static final int FLAG_TASKBAR_HIDDEN = 1 << 6; 118 119 private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_RESUMED | FLAG_TRANSITION_TO_RESUMED; 120 /** Equivalent to an int with all 1s for binary operation purposes */ 121 private static final int FLAGS_ALL = ~0; 122 123 private static final float TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f; 124 private static final float TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f; 125 private static final float TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT = 0.25f; 126 127 /** 128 * Delay for the taskbar fade-in. 129 * 130 * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions 131 * to launcher directly. The delay avoids the navbar to become briefly visible. The duration 132 * is the same as in SysUI, see http://shortn/_uNSbDoRUSr. 133 */ 134 private static final long TASKBAR_SHOW_DELAY_MS = 250; 135 136 private final AnimatedFloat mIconAlignment = 137 new AnimatedFloat(this::onIconAlignmentRatioChanged); 138 139 private TaskbarControllers mControllers; 140 private AnimatedFloat mTaskbarBackgroundAlpha; 141 private AnimatedFloat mTaskbarAlpha; 142 private AnimatedFloat mTaskbarCornerRoundness; 143 private MultiProperty mIconAlphaForHome; 144 private QuickstepLauncher mLauncher; 145 146 private Integer mPrevState; 147 private int mState; 148 private LauncherState mLauncherState = LauncherState.NORMAL; 149 150 // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds). 151 private long mLastUnlockTimeMs = 0; 152 153 private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener; 154 155 private boolean mIsAnimatingToLauncher; 156 157 private boolean mShouldDelayLauncherStateAnim; 158 159 // We skip any view synchronizations during init/destroy. 160 private boolean mCanSyncViews; 161 162 private boolean mIsQsbInline; 163 164 private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = 165 new DeviceProfile.OnDeviceProfileChangeListener() { 166 @Override 167 public void onDeviceProfileChanged(DeviceProfile dp) { 168 if (mIsQsbInline && !dp.isQsbInline) { 169 // We only modify QSB alpha if isQsbInline = true. If we switch to a DP 170 // where isQsbInline = false, then we need to reset the alpha. 171 mLauncher.getHotseat().setQsbAlpha(1f); 172 } 173 mIsQsbInline = dp.isQsbInline; 174 TaskbarLauncherStateController.this.updateIconAlphaForHome( 175 mIconAlphaForHome.getValue()); 176 } 177 }; 178 179 private final StateManager.StateListener<LauncherState> mStateListener = 180 new StateManager.StateListener<LauncherState>() { 181 182 @Override 183 public void onStateTransitionStart(LauncherState toState) { 184 if (toState != mLauncherState) { 185 // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a 186 // previous state transition was already running, so we update the new 187 // target. 188 mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION; 189 mLauncherState = toState; 190 } 191 updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true); 192 if (!mShouldDelayLauncherStateAnim) { 193 if (toState == LauncherState.NORMAL) { 194 applyState(QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION); 195 } else { 196 applyState(); 197 } 198 } 199 } 200 201 @Override 202 public void onStateTransitionComplete(LauncherState finalState) { 203 mLauncherState = finalState; 204 updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false); 205 applyState(); 206 boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT; 207 com.android.launcher3.taskbar.Utilities.setOverviewDragState( 208 mControllers, finalState.disallowTaskbarGlobalDrag(), 209 disallowLongClick, finalState.allowTaskbarInitialSplitSelection()); 210 } 211 }; 212 init(TaskbarControllers controllers, QuickstepLauncher launcher)213 public void init(TaskbarControllers controllers, QuickstepLauncher launcher) { 214 mCanSyncViews = false; 215 216 mControllers = controllers; 217 mLauncher = launcher; 218 219 mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline; 220 221 mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController 222 .getTaskbarBackgroundAlpha(); 223 mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha(); 224 mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness(); 225 mIconAlphaForHome = mControllers.taskbarViewController 226 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME); 227 228 resetIconAlignment(); 229 230 mLauncher.getStateManager().addStateListener(mStateListener); 231 mLauncherState = launcher.getStateManager().getState(); 232 applyState(0); 233 234 mCanSyncViews = true; 235 mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 236 } 237 onDestroy()238 public void onDestroy() { 239 mCanSyncViews = false; 240 241 mIconAlignment.finishAnimation(); 242 243 Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)"); 244 mLauncher.getHotseat().setIconsAlpha(1f); 245 mLauncher.getStateManager().removeStateListener(mStateListener); 246 247 mCanSyncViews = true; 248 mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 249 } 250 251 /** 252 * Creates a transition animation to the launcher activity. 253 * 254 * Warning: the resulting animation must be played, since this method has side effects on this 255 * controller's state. 256 */ createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)257 public Animator createAnimToLauncher(@NonNull LauncherState toState, 258 @NonNull RecentsAnimationCallbacks callbacks, long duration) { 259 // If going to overview, stash the task bar 260 // If going home, align the icons to hotseat 261 AnimatorSet animatorSet = new AnimatorSet(); 262 263 // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly. 264 TaskbarStashController stashController = mControllers.taskbarStashController; 265 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, 266 toState.isTaskbarStashed(mLauncher)); 267 if (DEBUG) { 268 Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false); 269 } 270 stashController.updateStateForFlag(FLAG_IN_APP, false); 271 272 updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true); 273 animatorSet.play(stashController.createApplyStateAnimator(duration)); 274 animatorSet.play(applyState(duration, false)); 275 276 if (mTaskBarRecentsAnimationListener != null) { 277 mTaskBarRecentsAnimationListener.endGestureStateOverride( 278 !mLauncher.isInState(LauncherState.OVERVIEW)); 279 } 280 mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); 281 callbacks.addListener(mTaskBarRecentsAnimationListener); 282 ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() -> 283 mTaskBarRecentsAnimationListener.endGestureStateOverride(true)); 284 return animatorSet; 285 } 286 isAnimatingToLauncher()287 public boolean isAnimatingToLauncher() { 288 return mIsAnimatingToLauncher; 289 } 290 setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)291 public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { 292 if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) { 293 // Animate the animation we have delayed immediately. This is usually triggered when 294 // the user has released their finger. 295 applyState(); 296 } 297 mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim; 298 } 299 300 /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */ updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim)301 public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) { 302 final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE); 303 final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE); 304 305 updateStateForFlag(FLAG_AWAKE, currIsAwake); 306 if (prevIsAwake != currIsAwake) { 307 // The screen is switching between on/off. When turning off, capture whether the 308 // launcher is active and memoize this state. 309 updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 310 prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE)); 311 } 312 313 boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED); 314 updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked); 315 316 // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the 317 // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in 318 // when the device is asleep, the second condition extends ensures that the transition from 319 // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar 320 // hide/reveal animation timings. 321 boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING) 322 || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE; 323 updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden); 324 325 if (skipAnim) { 326 applyState(0); 327 } else { 328 applyState(); 329 } 330 } 331 332 /** 333 * Updates the proper flag to change the state of the task bar. 334 * 335 * Note that this only updates the flag. {@link #applyState()} needs to be called separately. 336 * 337 * @param flag The flag to update. 338 * @param enabled Whether to enable the flag 339 */ updateStateForFlag(int flag, boolean enabled)340 public void updateStateForFlag(int flag, boolean enabled) { 341 if (enabled) { 342 mState |= flag; 343 } else { 344 mState &= ~flag; 345 } 346 } 347 hasAnyFlag(int flagMask)348 private boolean hasAnyFlag(int flagMask) { 349 return hasAnyFlag(mState, flagMask); 350 } 351 hasAnyFlag(int flags, int flagMask)352 private boolean hasAnyFlag(int flags, int flagMask) { 353 return (flags & flagMask) != 0; 354 } 355 applyState()356 public void applyState() { 357 applyState(mControllers.taskbarStashController.getStashDuration()); 358 } 359 applyState(long duration)360 public void applyState(long duration) { 361 applyState(duration, true); 362 } 363 applyState(boolean start)364 public Animator applyState(boolean start) { 365 return applyState(mControllers.taskbarStashController.getStashDuration(), start); 366 } 367 applyState(long duration, boolean start)368 public Animator applyState(long duration, boolean start) { 369 if (mControllers.taskbarActivityContext.isDestroyed()) { 370 return null; 371 } 372 Animator animator = null; 373 if (mPrevState == null || mPrevState != mState) { 374 // If this is our initial state, treat all flags as changed. 375 int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState; 376 377 if (DEBUG) { 378 String stateString; 379 if (mPrevState == null) { 380 stateString = getStateString(mState) + "(initial update)"; 381 } else { 382 stateString = formatFlagChange(mState, mPrevState, 383 TaskbarLauncherStateController::getStateString); 384 } 385 Log.d(TAG, "applyState: " + stateString 386 + ", duration: " + duration 387 + ", start: " + start); 388 } 389 mPrevState = mState; 390 animator = onStateChangeApplied(changedFlags, duration, start); 391 } 392 return animator; 393 } 394 onStateChangeApplied(int changedFlags, long duration, boolean start)395 private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) { 396 final boolean isInLauncher = isInLauncher(); 397 final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat(); 398 final float toAlignment = isIconAlignedWithHotseat ? 1 : 0; 399 boolean handleOpenFloatingViews = false; 400 if (DEBUG) { 401 Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher 402 + ", mLauncherState: " + mLauncherState 403 + ", toAlignment: " + toAlignment); 404 } 405 AnimatorSet animatorSet = new AnimatorSet(); 406 407 if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) { 408 boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION); 409 playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted); 410 411 if (launcherTransitionCompleted 412 && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) { 413 // We're about to be paused, set immediately to ensure seamless handoff. 414 updateStateForFlag(FLAG_RESUMED, false); 415 applyState(0 /* duration */); 416 } 417 if (mLauncherState == LauncherState.NORMAL) { 418 // We're changing state to home, should close open popups e.g. Taskbar AllApps 419 handleOpenFloatingViews = true; 420 } 421 } 422 423 if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE | FLAG_AWAKE)) { 424 animatorSet.addListener(new AnimatorListenerAdapter() { 425 @Override 426 public void onAnimationStart(Animator animation) { 427 mIsAnimatingToLauncher = isInLauncher; 428 429 TaskbarStashController stashController = 430 mControllers.taskbarStashController; 431 if (DEBUG) { 432 Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher); 433 } 434 stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher); 435 stashController.applyState(duration); 436 } 437 438 @Override 439 public void onAnimationEnd(Animator animation) { 440 mIsAnimatingToLauncher = false; 441 } 442 }); 443 444 // Handle closing open popups when going home/overview 445 handleOpenFloatingViews = true; 446 } 447 448 if (handleOpenFloatingViews && isInLauncher) { 449 AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext); 450 } 451 452 if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { 453 // Take note of the current time, as the taskbar is made visible again. 454 mLastUnlockTimeMs = SystemClock.elapsedRealtime(); 455 } 456 457 boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN); 458 float taskbarAlpha = isHidden ? 0 : 1; 459 if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) { 460 Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha); 461 462 taskbarVisibility.setDuration(duration); 463 if (isHidden) { 464 // Stash the transient taskbar once the taskbar is not visible. This reduces 465 // visual noise when unlocking the device afterwards. 466 animatorSet.addListener(new AnimatorListenerAdapter() { 467 @Override 468 public void onAnimationEnd(Animator animation) { 469 TaskbarStashController stashController = 470 mControllers.taskbarStashController; 471 stashController.updateAndAnimateTransientTaskbar( 472 /* stash */ true, /* duration */ 0); 473 } 474 }); 475 } else { 476 // delay the fade in animation a bit to reduce visual noise when waking up a device 477 // with a fingerprint reader. This should only be done when the device was woken 478 // up via fingerprint reader, however since this information is currently not 479 // available, opting to always delay the fade-in a bit. 480 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs; 481 taskbarVisibility.setStartDelay( 482 Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs)); 483 } 484 animatorSet.play(taskbarVisibility); 485 } 486 487 float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; 488 489 // Don't animate if background has reached desired value. 490 if (mTaskbarBackgroundAlpha.isAnimating() 491 || mTaskbarBackgroundAlpha.value != backgroundAlpha) { 492 mTaskbarBackgroundAlpha.cancelAnimation(); 493 if (DEBUG) { 494 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - " 495 + mTaskbarBackgroundAlpha.value 496 + " -> " + backgroundAlpha + ": " + duration); 497 } 498 499 boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; 500 boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; 501 boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; 502 503 float startDelay = 0; 504 // We want to delay the background from fading in so that the icons have time to move 505 // into the bounds of the background before it appears. 506 if (isInLauncherIconNotAligned) { 507 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 508 } else if (notInLauncherIconNotAligned) { 509 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 510 } 511 float newDuration = duration - startDelay; 512 if (isInLauncherIconIsAligned) { 513 // Make the background fade out faster so that it is gone by the time the 514 // icons move outside of the bounds of the background. 515 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT; 516 } 517 Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha 518 .animateToValue(backgroundAlpha) 519 .setDuration((long) newDuration); 520 taskbarBackgroundAlpha.setStartDelay((long) startDelay); 521 animatorSet.play(taskbarBackgroundAlpha); 522 } 523 524 float cornerRoundness = isInLauncher ? 0 : 1; 525 526 // Don't animate if corner roundness has reached desired value. 527 if (mTaskbarCornerRoundness.isAnimating() 528 || mTaskbarCornerRoundness.value != cornerRoundness) { 529 mTaskbarCornerRoundness.cancelAnimation(); 530 if (DEBUG) { 531 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - " 532 + mTaskbarCornerRoundness.value 533 + " -> " + cornerRoundness + ": " + duration); 534 } 535 animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness)); 536 } 537 538 // Keep isUnlockTransition in sync with its counterpart in 539 // TaskbarStashController#createAnimToIsStashed. 540 boolean isUnlockTransition = 541 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED); 542 if (isUnlockTransition) { 543 // When transitioning to unlocked, ensure the hotseat is fully visible from the 544 // beginning. The hotseat itself is animated by LauncherUnlockAnimationController. 545 mIconAlignment.cancelAnimation(); 546 // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual 547 // change in value 548 mIconAlignment.updateValue(toAlignment); 549 } else if (mIconAlignment.isAnimatingToValue(toAlignment) 550 || mIconAlignment.isSettledOnValue(toAlignment)) { 551 // Already at desired value, but make sure we run the callback at the end. 552 animatorSet.addListener(AnimatorListeners.forEndCallback( 553 this::onIconAlignmentRatioChanged)); 554 } else { 555 mIconAlignment.cancelAnimation(); 556 ObjectAnimator iconAlignAnim = mIconAlignment 557 .animateToValue(toAlignment) 558 .setDuration(duration); 559 if (DEBUG) { 560 Log.d(TAG, "onStateChangeApplied - iconAlignment - " 561 + mIconAlignment.value 562 + " -> " + toAlignment + ": " + duration); 563 } 564 animatorSet.play(iconAlignAnim); 565 } 566 567 animatorSet.setInterpolator(EMPHASIZED); 568 569 if (start) { 570 animatorSet.start(); 571 } 572 return animatorSet; 573 } 574 575 /** 576 * Whether the taskbar is aligned with the hotseat in the current/target launcher state. 577 * 578 * This refers to the intended state - a transition to this state might be in progress. 579 */ isTaskbarAlignedWithHotseat()580 public boolean isTaskbarAlignedWithHotseat() { 581 return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); 582 } 583 584 /** 585 * Returns if icons should be aligned to hotseat in the current transition 586 */ isIconAlignedWithHotseat()587 public boolean isIconAlignedWithHotseat() { 588 if (isInLauncher()) { 589 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 590 boolean willStashVisually = isInStashedState 591 && mControllers.taskbarStashController.supportsVisualStashing(); 592 boolean isTaskbarAlignedWithHotseat = 593 mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); 594 return isTaskbarAlignedWithHotseat && !willStashVisually; 595 } else { 596 return false; 597 } 598 } 599 600 /** 601 * Returns if the current Launcher state has hotseat on top of other elemnets. 602 */ isInHotseatOnTopStates()603 public boolean isInHotseatOnTopStates() { 604 return mLauncherState != LauncherState.ALL_APPS; 605 } 606 isInOverview()607 boolean isInOverview() { 608 return mLauncherState == LauncherState.OVERVIEW; 609 } 610 playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)611 private void playStateTransitionAnim(AnimatorSet animatorSet, long duration, 612 boolean committed) { 613 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 614 TaskbarStashController stashController = mControllers.taskbarStashController; 615 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState); 616 Animator stashAnimator = stashController.createApplyStateAnimator(duration); 617 if (stashAnimator != null) { 618 stashAnimator.addListener(new AnimatorListenerAdapter() { 619 @Override 620 public void onAnimationEnd(Animator animation) { 621 if (isInStashedState && committed) { 622 // Reset hotseat alpha to default 623 Log.d("b/260135164", 624 "playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)"); 625 mLauncher.getHotseat().setIconsAlpha(1); 626 } 627 } 628 629 @Override 630 public void onAnimationStart(Animator animation) { 631 if (mLauncher.getHotseat().getIconsAlpha() > 0) { 632 updateIconAlphaForHome(mLauncher.getHotseat().getIconsAlpha()); 633 } 634 } 635 }); 636 animatorSet.play(stashAnimator); 637 } 638 639 // Translate back to 0 at a shorter or same duration as the icon alignment animation. 640 // This ensures there is no jump after switching to hotseat, e.g. when swiping up from 641 // overview to home. When not in app, we do duration / 2 just to make it feel snappier. 642 long resetDuration = mControllers.taskbarStashController.isInApp() 643 ? duration 644 : duration / 2; 645 if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration) 646 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) { 647 animatorSet.play(mControllers.taskbarTranslationController 648 .createAnimToResetTranslation(resetDuration)); 649 } 650 } 651 652 /** Whether the launcher is considered active. */ isInLauncher()653 private boolean isInLauncher() { 654 if (hasAnyFlag(FLAG_AWAKE)) { 655 return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE); 656 } else { 657 return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE); 658 } 659 } 660 661 /** 662 * Resets and updates the icon alignment. 663 */ resetIconAlignment()664 protected void resetIconAlignment() { 665 mIconAlignment.finishAnimation(); 666 onIconAlignmentRatioChanged(); 667 } 668 onIconAlignmentRatioChanged()669 private void onIconAlignmentRatioChanged() { 670 float currentValue = mIconAlphaForHome.getValue(); 671 boolean taskbarWillBeVisible = mIconAlignment.value < 1; 672 boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0) 673 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0); 674 675 mControllers.taskbarViewController.setLauncherIconAlignment( 676 mIconAlignment.value, mLauncher.getDeviceProfile()); 677 mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value); 678 // Switch taskbar and hotseat in last frame 679 updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0); 680 681 // Sync the first frame where we swap taskbar and hotseat. 682 if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) { 683 ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(), 684 mControllers.taskbarActivityContext.getDragLayer(), 685 () -> { 686 }); 687 } 688 } 689 updateIconAlphaForHome(float alpha)690 private void updateIconAlphaForHome(float alpha) { 691 if (mControllers.taskbarActivityContext.isDestroyed()) { 692 Log.e("b/260135164", "updateIconAlphaForHome is called after Taskbar is destroyed", 693 new Exception()); 694 return; 695 } 696 mIconAlphaForHome.setValue(alpha); 697 boolean hotseatVisible = alpha == 0 698 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned() 699 && mIconAlignment.value > 0); 700 /* 701 * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets 702 * should not be visible at the same time. 703 */ 704 Log.d("b/260135164", 705 "updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0) 706 + "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent); 707 mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0); 708 if (mIsQsbInline) { 709 mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0); 710 } 711 } 712 713 private final class TaskBarRecentsAnimationListener implements 714 RecentsAnimationCallbacks.RecentsAnimationListener { 715 private final RecentsAnimationCallbacks mCallbacks; 716 TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks)717 TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) { 718 mCallbacks = callbacks; 719 } 720 721 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)722 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 723 boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW); 724 endGestureStateOverride(!isInOverview); 725 } 726 727 @Override onRecentsAnimationFinished(RecentsAnimationController controller)728 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 729 endGestureStateOverride(!controller.getFinishTargetIsLauncher()); 730 } 731 endGestureStateOverride(boolean finishedToApp)732 private void endGestureStateOverride(boolean finishedToApp) { 733 mCallbacks.removeListener(this); 734 mTaskBarRecentsAnimationListener = null; 735 ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null); 736 737 // Update the resumed state immediately to ensure a seamless handoff 738 boolean launcherResumed = !finishedToApp; 739 updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false); 740 updateStateForFlag(FLAG_RESUMED, launcherResumed); 741 applyState(); 742 743 TaskbarStashController controller = mControllers.taskbarStashController; 744 if (DEBUG) { 745 Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp); 746 } 747 controller.updateStateForFlag(FLAG_IN_APP, finishedToApp); 748 controller.applyState(); 749 } 750 } 751 getStateString(int flags)752 private static String getStateString(int flags) { 753 StringJoiner result = new StringJoiner("|"); 754 appendFlag(result, flags, FLAG_RESUMED, "resumed"); 755 appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed"); 756 appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION, 757 "launcher_in_state_transition"); 758 appendFlag(result, flags, FLAG_AWAKE, "awake"); 759 appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 760 "was_active_while_awake"); 761 appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked"); 762 return result.toString(); 763 } 764 dumpLogs(String prefix, PrintWriter pw)765 protected void dumpLogs(String prefix, PrintWriter pw) { 766 pw.println(prefix + "TaskbarLauncherStateController:"); 767 pw.println(String.format( 768 "%s\tmIconAlignment=%.2f", 769 prefix, 770 mIconAlignment.value)); 771 pw.println(String.format( 772 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value)); 773 pw.println(String.format( 774 "%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue())); 775 pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState))); 776 pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState))); 777 pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState)); 778 pw.println(String.format( 779 "%s\tmIsAnimatingToLauncher=%b", 780 prefix, 781 mIsAnimatingToLauncher)); 782 pw.println(String.format( 783 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim)); 784 } 785 } 786