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.app.animation.Interpolators.EMPHASIZED; 19 import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED; 20 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; 21 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE; 22 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME; 23 import static com.android.launcher3.util.FlagDebugUtils.appendFlag; 24 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; 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 // TODO(b/279514548) Cleans up bad state that can occur when user interacts with 206 // taskbar on top of transparent activity. 207 if (finalState == LauncherState.NORMAL && mLauncher.hasBeenResumed()) { 208 updateStateForFlag(FLAG_RESUMED, true); 209 } 210 applyState(); 211 boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT; 212 com.android.launcher3.taskbar.Utilities.setOverviewDragState( 213 mControllers, finalState.disallowTaskbarGlobalDrag(), 214 disallowLongClick, finalState.allowTaskbarInitialSplitSelection()); 215 } 216 }; 217 218 /** Initializes the controller instance, and applies the initial state immediately. */ init(TaskbarControllers controllers, QuickstepLauncher launcher, int sysuiStateFlags)219 public void init(TaskbarControllers controllers, QuickstepLauncher launcher, 220 int sysuiStateFlags) { 221 mCanSyncViews = false; 222 223 mControllers = controllers; 224 mLauncher = launcher; 225 226 mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline; 227 228 mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController 229 .getTaskbarBackgroundAlpha(); 230 mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha(); 231 mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness(); 232 mIconAlphaForHome = mControllers.taskbarViewController 233 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME); 234 235 resetIconAlignment(); 236 237 mLauncher.getStateManager().addStateListener(mStateListener); 238 mLauncherState = launcher.getStateManager().getState(); 239 updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false); 240 241 applyState(0); 242 243 mCanSyncViews = true; 244 mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 245 } 246 onDestroy()247 public void onDestroy() { 248 mCanSyncViews = false; 249 250 mIconAlignment.finishAnimation(); 251 252 mLauncher.getHotseat().setIconsAlpha(1f); 253 mLauncher.getStateManager().removeStateListener(mStateListener); 254 255 mCanSyncViews = true; 256 mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 257 } 258 259 /** 260 * Creates a transition animation to the launcher activity. 261 * 262 * Warning: the resulting animation must be played, since this method has side effects on this 263 * controller's state. 264 */ createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)265 public Animator createAnimToLauncher(@NonNull LauncherState toState, 266 @NonNull RecentsAnimationCallbacks callbacks, long duration) { 267 // If going to overview, stash the task bar 268 // If going home, align the icons to hotseat 269 AnimatorSet animatorSet = new AnimatorSet(); 270 271 // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly. 272 TaskbarStashController stashController = mControllers.taskbarStashController; 273 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, 274 toState.isTaskbarStashed(mLauncher)); 275 if (DEBUG) { 276 Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false); 277 } 278 stashController.updateStateForFlag(FLAG_IN_APP, false); 279 280 updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, true); 281 animatorSet.play(stashController.createApplyStateAnimator(duration)); 282 animatorSet.play(applyState(duration, false)); 283 284 if (mTaskBarRecentsAnimationListener != null) { 285 mTaskBarRecentsAnimationListener.endGestureStateOverride( 286 !mLauncher.isInState(LauncherState.OVERVIEW)); 287 } 288 mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); 289 callbacks.addListener(mTaskBarRecentsAnimationListener); 290 ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() -> 291 mTaskBarRecentsAnimationListener.endGestureStateOverride(true)); 292 return animatorSet; 293 } 294 isAnimatingToLauncher()295 public boolean isAnimatingToLauncher() { 296 return mIsAnimatingToLauncher; 297 } 298 setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)299 public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { 300 if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) { 301 // Animate the animation we have delayed immediately. This is usually triggered when 302 // the user has released their finger. 303 applyState(); 304 } 305 mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim; 306 } 307 308 /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */ updateStateForSysuiFlags(int systemUiStateFlags)309 public void updateStateForSysuiFlags(int systemUiStateFlags) { 310 updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true); 311 } 312 updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState)313 private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) { 314 final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE); 315 final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE); 316 317 updateStateForFlag(FLAG_AWAKE, currIsAwake); 318 if (prevIsAwake != currIsAwake) { 319 // The screen is switching between on/off. When turning off, capture whether the 320 // launcher is active and memoize this state. 321 updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 322 prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE)); 323 } 324 325 boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED); 326 updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked); 327 328 // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the 329 // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in 330 // when the device is asleep, the second condition extends ensures that the transition from 331 // and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar 332 // hide/reveal animation timings. 333 boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING) 334 || (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE; 335 updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden); 336 337 if (applyState) { 338 applyState(); 339 } 340 } 341 342 /** 343 * Updates the proper flag to change the state of the task bar. 344 * 345 * Note that this only updates the flag. {@link #applyState()} needs to be called separately. 346 * 347 * @param flag The flag to update. 348 * @param enabled Whether to enable the flag 349 */ updateStateForFlag(int flag, boolean enabled)350 public void updateStateForFlag(int flag, boolean enabled) { 351 if (enabled) { 352 mState |= flag; 353 } else { 354 mState &= ~flag; 355 } 356 } 357 hasAnyFlag(int flagMask)358 private boolean hasAnyFlag(int flagMask) { 359 return hasAnyFlag(mState, flagMask); 360 } 361 hasAnyFlag(int flags, int flagMask)362 private boolean hasAnyFlag(int flags, int flagMask) { 363 return (flags & flagMask) != 0; 364 } 365 applyState()366 public void applyState() { 367 applyState(mControllers.taskbarStashController.getStashDuration()); 368 } 369 applyState(long duration)370 public void applyState(long duration) { 371 applyState(duration, true); 372 } 373 applyState(long duration, boolean start)374 public Animator applyState(long duration, boolean start) { 375 if (mControllers.taskbarActivityContext.isDestroyed()) { 376 return null; 377 } 378 Animator animator = null; 379 if (mPrevState == null || mPrevState != mState) { 380 // If this is our initial state, treat all flags as changed. 381 int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState; 382 383 if (DEBUG) { 384 String stateString; 385 if (mPrevState == null) { 386 stateString = getStateString(mState) + "(initial update)"; 387 } else { 388 stateString = formatFlagChange(mState, mPrevState, 389 TaskbarLauncherStateController::getStateString); 390 } 391 Log.d(TAG, "applyState: " + stateString 392 + ", duration: " + duration 393 + ", start: " + start); 394 } 395 mPrevState = mState; 396 animator = onStateChangeApplied(changedFlags, duration, start); 397 } 398 return animator; 399 } 400 onStateChangeApplied(int changedFlags, long duration, boolean start)401 private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) { 402 final boolean isInLauncher = isInLauncher(); 403 final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat(); 404 final float toAlignment = isIconAlignedWithHotseat ? 1 : 0; 405 boolean handleOpenFloatingViews = false; 406 if (DEBUG) { 407 Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher 408 + ", mLauncherState: " + mLauncherState 409 + ", toAlignment: " + toAlignment); 410 } 411 mControllers.bubbleControllers.ifPresent(controllers -> { 412 // Show the bubble bar when on launcher home or in overview. 413 boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL; 414 boolean onOverview = mLauncherState == LauncherState.OVERVIEW; 415 controllers.bubbleStashController.setBubblesShowingOnHome(onHome); 416 controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview); 417 }); 418 419 AnimatorSet animatorSet = new AnimatorSet(); 420 421 if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) { 422 boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION); 423 playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted); 424 425 if (launcherTransitionCompleted 426 && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) { 427 // We're about to be paused, set immediately to ensure seamless handoff. 428 updateStateForFlag(FLAG_RESUMED, false); 429 applyState(0 /* duration */); 430 } 431 if (mLauncherState == LauncherState.NORMAL) { 432 // We're changing state to home, should close open popups e.g. Taskbar AllApps 433 handleOpenFloatingViews = true; 434 } 435 if (mLauncherState == LauncherState.OVERVIEW) { 436 // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability 437 mControllers.taskbarActivityContext.notifyUpdateLayoutParams(); 438 } 439 } 440 441 if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) { 442 animatorSet.addListener(new AnimatorListenerAdapter() { 443 @Override 444 public void onAnimationStart(Animator animation) { 445 mIsAnimatingToLauncher = isInLauncher; 446 447 TaskbarStashController stashController = 448 mControllers.taskbarStashController; 449 if (DEBUG) { 450 Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher); 451 } 452 stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher); 453 stashController.applyState(duration); 454 } 455 456 @Override 457 public void onAnimationEnd(Animator animation) { 458 mIsAnimatingToLauncher = false; 459 } 460 }); 461 462 // Handle closing open popups when going home/overview 463 handleOpenFloatingViews = true; 464 } 465 466 if (handleOpenFloatingViews && isInLauncher) { 467 AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext); 468 } 469 470 if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { 471 // Take note of the current time, as the taskbar is made visible again. 472 mLastUnlockTimeMs = SystemClock.elapsedRealtime(); 473 } 474 475 boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN); 476 float taskbarAlpha = isHidden ? 0 : 1; 477 if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) { 478 Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha); 479 480 taskbarVisibility.setDuration(duration); 481 if (isHidden) { 482 // Stash the transient taskbar once the taskbar is not visible. This reduces 483 // visual noise when unlocking the device afterwards. 484 animatorSet.addListener(new AnimatorListenerAdapter() { 485 @Override 486 public void onAnimationEnd(Animator animation) { 487 TaskbarStashController stashController = 488 mControllers.taskbarStashController; 489 stashController.updateAndAnimateTransientTaskbar( 490 /* stash */ true, /* bubblesShouldFollow */ true); 491 } 492 }); 493 } else { 494 // delay the fade in animation a bit to reduce visual noise when waking up a device 495 // with a fingerprint reader. This should only be done when the device was woken 496 // up via fingerprint reader, however since this information is currently not 497 // available, opting to always delay the fade-in a bit. 498 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs; 499 taskbarVisibility.setStartDelay( 500 Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs)); 501 } 502 animatorSet.play(taskbarVisibility); 503 } 504 505 float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; 506 507 // Don't animate if background has reached desired value. 508 if (mTaskbarBackgroundAlpha.isAnimating() 509 || mTaskbarBackgroundAlpha.value != backgroundAlpha) { 510 mTaskbarBackgroundAlpha.cancelAnimation(); 511 if (DEBUG) { 512 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - " 513 + mTaskbarBackgroundAlpha.value 514 + " -> " + backgroundAlpha + ": " + duration); 515 } 516 517 boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; 518 boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; 519 boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; 520 521 float startDelay = 0; 522 // We want to delay the background from fading in so that the icons have time to move 523 // into the bounds of the background before it appears. 524 if (isInLauncherIconNotAligned) { 525 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 526 } else if (notInLauncherIconNotAligned) { 527 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 528 } 529 float newDuration = duration - startDelay; 530 if (isInLauncherIconIsAligned) { 531 // Make the background fade out faster so that it is gone by the time the 532 // icons move outside of the bounds of the background. 533 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT; 534 } 535 Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha 536 .animateToValue(backgroundAlpha) 537 .setDuration((long) newDuration); 538 taskbarBackgroundAlpha.setStartDelay((long) startDelay); 539 animatorSet.play(taskbarBackgroundAlpha); 540 } 541 542 float cornerRoundness = isInLauncher ? 0 : 1; 543 544 // Don't animate if corner roundness has reached desired value. 545 if (mTaskbarCornerRoundness.isAnimating() 546 || mTaskbarCornerRoundness.value != cornerRoundness) { 547 mTaskbarCornerRoundness.cancelAnimation(); 548 if (DEBUG) { 549 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - " 550 + mTaskbarCornerRoundness.value 551 + " -> " + cornerRoundness + ": " + duration); 552 } 553 animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness)); 554 } 555 556 // Keep isUnlockTransition in sync with its counterpart in 557 // TaskbarStashController#createAnimToIsStashed. 558 boolean isUnlockTransition = 559 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED); 560 if (isUnlockTransition) { 561 // When transitioning to unlocked, ensure the hotseat is fully visible from the 562 // beginning. The hotseat itself is animated by LauncherUnlockAnimationController. 563 mIconAlignment.cancelAnimation(); 564 // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual 565 // change in value 566 mIconAlignment.updateValue(toAlignment); 567 568 // Make sure FLAG_IN_APP is set when launching applications from keyguard. 569 if (!isInLauncher) { 570 mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true); 571 mControllers.taskbarStashController.applyState(0); 572 } 573 } else if (mIconAlignment.isAnimatingToValue(toAlignment) 574 || mIconAlignment.isSettledOnValue(toAlignment)) { 575 // Already at desired value, but make sure we run the callback at the end. 576 animatorSet.addListener(AnimatorListeners.forEndCallback( 577 this::onIconAlignmentRatioChanged)); 578 } else { 579 mIconAlignment.cancelAnimation(); 580 ObjectAnimator iconAlignAnim = mIconAlignment 581 .animateToValue(toAlignment) 582 .setDuration(duration); 583 if (DEBUG) { 584 Log.d(TAG, "onStateChangeApplied - iconAlignment - " 585 + mIconAlignment.value 586 + " -> " + toAlignment + ": " + duration); 587 } 588 animatorSet.play(iconAlignAnim); 589 } 590 591 animatorSet.setInterpolator(EMPHASIZED); 592 593 if (start) { 594 animatorSet.start(); 595 } 596 return animatorSet; 597 } 598 599 /** 600 * Whether the taskbar is aligned with the hotseat in the current/target launcher state. 601 * 602 * This refers to the intended state - a transition to this state might be in progress. 603 */ isTaskbarAlignedWithHotseat()604 public boolean isTaskbarAlignedWithHotseat() { 605 return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); 606 } 607 608 /** 609 * Returns if icons should be aligned to hotseat in the current transition 610 */ isIconAlignedWithHotseat()611 public boolean isIconAlignedWithHotseat() { 612 if (isInLauncher()) { 613 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 614 boolean willStashVisually = isInStashedState 615 && mControllers.taskbarStashController.supportsVisualStashing(); 616 boolean isTaskbarAlignedWithHotseat = 617 mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); 618 return isTaskbarAlignedWithHotseat && !willStashVisually; 619 } else { 620 return false; 621 } 622 } 623 624 /** 625 * Returns if the current Launcher state has hotseat on top of other elemnets. 626 */ isInHotseatOnTopStates()627 public boolean isInHotseatOnTopStates() { 628 return mLauncherState != LauncherState.ALL_APPS; 629 } 630 isInOverview()631 boolean isInOverview() { 632 return mLauncherState == LauncherState.OVERVIEW; 633 } 634 playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)635 private void playStateTransitionAnim(AnimatorSet animatorSet, long duration, 636 boolean committed) { 637 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 638 TaskbarStashController stashController = mControllers.taskbarStashController; 639 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState); 640 Animator stashAnimator = stashController.createApplyStateAnimator(duration); 641 if (stashAnimator != null) { 642 stashAnimator.addListener(new AnimatorListenerAdapter() { 643 @Override 644 public void onAnimationEnd(Animator animation) { 645 if (isInStashedState && committed) { 646 // Reset hotseat alpha to default 647 mLauncher.getHotseat().setIconsAlpha(1); 648 } 649 } 650 651 @Override 652 public void onAnimationStart(Animator animation) { 653 if (mLauncher.getHotseat().getIconsAlpha() > 0) { 654 updateIconAlphaForHome(mLauncher.getHotseat().getIconsAlpha()); 655 } 656 } 657 }); 658 animatorSet.play(stashAnimator); 659 } 660 661 // Translate back to 0 at a shorter or same duration as the icon alignment animation. 662 // This ensures there is no jump after switching to hotseat, e.g. when swiping up from 663 // overview to home. When not in app, we do duration / 2 just to make it feel snappier. 664 long resetDuration = mControllers.taskbarStashController.isInApp() 665 ? duration 666 : duration / 2; 667 if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration) 668 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) { 669 animatorSet.play(mControllers.taskbarTranslationController 670 .createAnimToResetTranslation(resetDuration)); 671 } 672 } 673 674 /** Whether the launcher is considered active. */ isInLauncher()675 private boolean isInLauncher() { 676 if (hasAnyFlag(FLAG_AWAKE)) { 677 return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE); 678 } else { 679 return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE); 680 } 681 } 682 683 /** 684 * Resets and updates the icon alignment. 685 */ resetIconAlignment()686 protected void resetIconAlignment() { 687 mIconAlignment.finishAnimation(); 688 onIconAlignmentRatioChanged(); 689 } 690 onIconAlignmentRatioChanged()691 private void onIconAlignmentRatioChanged() { 692 float currentValue = mIconAlphaForHome.getValue(); 693 boolean taskbarWillBeVisible = mIconAlignment.value < 1; 694 boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0) 695 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0); 696 697 mControllers.taskbarViewController.setLauncherIconAlignment( 698 mIconAlignment.value, mLauncher.getDeviceProfile()); 699 mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value); 700 // Switch taskbar and hotseat in last frame 701 updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0); 702 703 // Sync the first frame where we swap taskbar and hotseat. 704 if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) { 705 ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(), 706 mControllers.taskbarActivityContext.getDragLayer(), 707 () -> { 708 }); 709 } 710 } 711 updateIconAlphaForHome(float alpha)712 private void updateIconAlphaForHome(float alpha) { 713 if (mControllers.taskbarActivityContext.isDestroyed()) { 714 return; 715 } 716 mIconAlphaForHome.setValue(alpha); 717 boolean hotseatVisible = alpha == 0 718 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned() 719 && mIconAlignment.value > 0); 720 /* 721 * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets 722 * should not be visible at the same time. 723 */ 724 mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0); 725 if (mIsQsbInline) { 726 mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0); 727 } 728 } 729 730 private final class TaskBarRecentsAnimationListener implements 731 RecentsAnimationCallbacks.RecentsAnimationListener { 732 private final RecentsAnimationCallbacks mCallbacks; 733 TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks)734 TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) { 735 mCallbacks = callbacks; 736 } 737 738 @Override onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas)739 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 740 boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW); 741 endGestureStateOverride(!isInOverview); 742 } 743 744 @Override onRecentsAnimationFinished(RecentsAnimationController controller)745 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 746 endGestureStateOverride(!controller.getFinishTargetIsLauncher()); 747 } 748 endGestureStateOverride(boolean finishedToApp)749 private void endGestureStateOverride(boolean finishedToApp) { 750 mCallbacks.removeListener(this); 751 mTaskBarRecentsAnimationListener = null; 752 ((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null); 753 754 // Update the resumed state immediately to ensure a seamless handoff 755 boolean launcherResumed = !finishedToApp; 756 updateStateForFlag(FLAG_TRANSITION_TO_RESUMED, false); 757 updateStateForFlag(FLAG_RESUMED, launcherResumed); 758 applyState(); 759 760 TaskbarStashController controller = mControllers.taskbarStashController; 761 if (DEBUG) { 762 Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp); 763 } 764 controller.updateStateForFlag(FLAG_IN_APP, finishedToApp); 765 controller.applyState(); 766 } 767 } 768 getStateString(int flags)769 private static String getStateString(int flags) { 770 StringJoiner result = new StringJoiner("|"); 771 appendFlag(result, flags, FLAG_RESUMED, "resumed"); 772 appendFlag(result, flags, FLAG_TRANSITION_TO_RESUMED, "transition_to_resumed"); 773 appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION, 774 "launcher_in_state_transition"); 775 appendFlag(result, flags, FLAG_AWAKE, "awake"); 776 appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 777 "was_active_while_awake"); 778 appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked"); 779 return result.toString(); 780 } 781 dumpLogs(String prefix, PrintWriter pw)782 protected void dumpLogs(String prefix, PrintWriter pw) { 783 pw.println(prefix + "TaskbarLauncherStateController:"); 784 pw.println(String.format( 785 "%s\tmIconAlignment=%.2f", 786 prefix, 787 mIconAlignment.value)); 788 pw.println(String.format( 789 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value)); 790 pw.println(String.format( 791 "%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue())); 792 pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState))); 793 pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState))); 794 pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState)); 795 pw.println(String.format( 796 "%s\tmIsAnimatingToLauncher=%b", 797 prefix, 798 mIsAnimatingToLauncher)); 799 pw.println(String.format( 800 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim)); 801 } 802 } 803