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