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.app.animation.Interpolators.FINAL_FRAME; 20 import static com.android.app.animation.Interpolators.INSTANT; 21 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 22 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT; 23 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH; 24 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; 25 import static com.android.launcher3.Utilities.isRtl; 26 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP; 27 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW; 28 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE; 29 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_FOR_BUBBLES; 30 import static com.android.launcher3.taskbar.TaskbarStashController.UNLOCK_TRANSITION_MEMOIZATION_MS; 31 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME; 32 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS; 33 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS; 34 import static com.android.launcher3.util.FlagDebugUtils.appendFlag; 35 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; 36 import static com.android.quickstep.fallback.RecentsStateUtilsKt.toLauncherState; 37 import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden; 38 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE; 39 40 import android.animation.Animator; 41 import android.animation.AnimatorListenerAdapter; 42 import android.animation.AnimatorSet; 43 import android.animation.ObjectAnimator; 44 import android.os.SystemClock; 45 import android.util.Log; 46 import android.view.animation.Interpolator; 47 48 import androidx.annotation.NonNull; 49 import androidx.annotation.Nullable; 50 51 import com.android.app.animation.Interpolators; 52 import com.android.launcher3.AbstractFloatingView; 53 import com.android.launcher3.DeviceProfile; 54 import com.android.launcher3.Hotseat; 55 import com.android.launcher3.Hotseat.HotseatQsbAlphaId; 56 import com.android.launcher3.LauncherState; 57 import com.android.launcher3.QuickstepTransitionManager; 58 import com.android.launcher3.Utilities; 59 import com.android.launcher3.anim.AnimatedFloat; 60 import com.android.launcher3.anim.AnimatorListeners; 61 import com.android.launcher3.statemanager.StateManager; 62 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState; 63 import com.android.launcher3.uioverrides.QuickstepLauncher; 64 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; 65 import com.android.quickstep.RecentsAnimationCallbacks; 66 import com.android.quickstep.RecentsAnimationController; 67 import com.android.quickstep.fallback.RecentsState; 68 import com.android.quickstep.fallback.window.RecentsDisplayModel; 69 import com.android.quickstep.fallback.window.RecentsWindowFlags; 70 import com.android.quickstep.fallback.window.RecentsWindowManager; 71 import com.android.quickstep.util.ScalingWorkspaceRevealAnim; 72 import com.android.quickstep.util.SystemUiFlagUtils; 73 import com.android.quickstep.views.RecentsView; 74 import com.android.systemui.animation.ViewRootSync; 75 import com.android.systemui.shared.recents.model.ThumbnailData; 76 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; 77 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 78 79 import java.io.PrintWriter; 80 import java.util.HashMap; 81 import java.util.StringJoiner; 82 import java.util.function.Consumer; 83 84 /** 85 * Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate 86 * the task bar accordingly. 87 */ 88 public class TaskbarLauncherStateController { 89 90 private static final String TAG = "TaskbarLauncherStateController"; 91 private static final boolean DEBUG = false; 92 93 /** Launcher activity is visible and focused. */ 94 public static final int FLAG_VISIBLE = 1 << 0; 95 96 /** 97 * A external transition / animation is running that will result in FLAG_VISIBLE being set. 98 **/ 99 public static final int FLAG_TRANSITION_TO_VISIBLE = 1 << 1; 100 101 /** 102 * Set while the launcher state machine is performing a state transition, see {@link 103 * StateManager.StateListener}. 104 */ 105 public static final int FLAG_LAUNCHER_IN_STATE_TRANSITION = 1 << 2; 106 107 /** 108 * Whether the screen is currently on, or is transitioning to be on. 109 * 110 * This is cleared as soon as the screen begins to transition off. 111 */ 112 private static final int FLAG_AWAKE = 1 << 3; 113 114 /** 115 * Captures whether the launcher was active at the time the FLAG_AWAKE was cleared. 116 * Always cleared when FLAG_AWAKE is set. 117 * <p> 118 * FLAG_RESUMED will be cleared when the device is asleep, since all apps get paused at this 119 * point. Thus, this flag indicates whether the launcher will be shown when the device wakes up 120 * again. 121 */ 122 private static final int FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE = 1 << 4; 123 124 /** 125 * Whether the device is currently locked. 126 * <ul> 127 * <li>While locked, the taskbar is always stashed.<li/> 128 * <li>Navbar animations on FLAG_DEVICE_LOCKED transitions will get special treatment.</li> 129 * </ul> 130 */ 131 private static final int FLAG_DEVICE_LOCKED = 1 << 5; 132 133 /** 134 * Whether the complete taskbar is completely hidden (neither visible stashed or unstashed). 135 * This is tracked to allow a nice transition of the taskbar before SysUI forces it away by 136 * hiding the inset. 137 * 138 * This flag is predominanlty set while FLAG_DEVICE_LOCKED is set, thus the taskbar's invisible 139 * resting state while hidden is stashed. 140 */ 141 private static final int FLAG_TASKBAR_HIDDEN = 1 << 6; 142 143 private static final int FLAGS_LAUNCHER_ACTIVE = FLAG_VISIBLE | FLAG_TRANSITION_TO_VISIBLE; 144 /** Equivalent to an int with all 1s for binary operation purposes */ 145 private static final int FLAGS_ALL = ~0; 146 147 private static final float TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f; 148 private static final float TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT = 0.33f; 149 private static final float TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT = 0.25f; 150 151 /** 152 * Delay for the taskbar fade-in. 153 * 154 * Helps to avoid visual noise when unlocking successfully via SFPS, and the device transitions 155 * to launcher directly. The delay avoids the navbar to become briefly visible. The duration 156 * is the same as in SysUI, see http://shortn/_uNSbDoRUSr. 157 */ 158 private static final long TASKBAR_SHOW_DELAY_MS = 250; 159 160 private final AnimatedFloat mIconAlignment = 161 new AnimatedFloat(this::onIconAlignmentRatioChanged); 162 163 private TaskbarControllers mControllers; 164 private AnimatedFloat mTaskbarBackgroundAlpha; 165 private AnimatedFloat mTaskbarAlpha; 166 private AnimatedFloat mTaskbarCornerRoundness; 167 private MultiProperty mTaskbarAlphaForHome; 168 private @Nullable Animator mHotseatTranslationXAnimation; 169 private QuickstepLauncher mLauncher; 170 171 private boolean mIsDestroyed = false; 172 private Integer mPrevState; 173 private int mState; 174 private LauncherState mLauncherState = LauncherState.NORMAL; 175 private boolean mSkipNextRecentsAnimEnd; 176 177 // Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds). 178 private long mLastRemoveTaskbarHiddenTimeMs = 0; 179 /** 180 * Time when FLAG_DEVICE_LOCKED was last cleared, plus 181 * {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS} 182 */ 183 private long mLastUnlockTransitionTimeout; 184 185 private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener; 186 187 private boolean mIsAnimatingToLauncher; 188 189 private boolean mShouldDelayLauncherStateAnim; 190 191 private @Nullable BubbleBarLocation mBubbleBarLocation; 192 193 // We skip any view synchronizations during init/destroy. 194 private boolean mCanSyncViews; 195 196 private boolean mIsQsbInline; 197 198 private RecentsAnimationCallbacks mRecentsAnimationCallbacks; 199 200 private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = 201 new DeviceProfile.OnDeviceProfileChangeListener() { 202 @Override 203 public void onDeviceProfileChanged(DeviceProfile dp) { 204 if (mIsQsbInline && !dp.isQsbInline) { 205 // We only modify QSB alpha if isQsbInline = true. If we switch to a DP 206 // where isQsbInline = false, then we need to reset the alpha. 207 mLauncher.getHotseat().setQsbAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 208 } 209 mIsQsbInline = dp.isQsbInline; 210 TaskbarLauncherStateController.this.updateIconAlphaForHome( 211 mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 212 TaskbarLauncherStateController.this.onBubbleBarLocationChanged( 213 mBubbleBarLocation, /* animate = */ false); 214 } 215 }; 216 217 private final StateManager.StateListener<LauncherState> mStateListener = 218 new StateManager.StateListener<>() { 219 220 @Override 221 public void onStateTransitionStart(LauncherState toState) { 222 if (toState != mLauncherState) { 223 // Treat FLAG_LAUNCHER_IN_STATE_TRANSITION as a changed flag even if a 224 // previous state transition was already running, so we update the new 225 // target. 226 mPrevState &= ~FLAG_LAUNCHER_IN_STATE_TRANSITION; 227 mLauncherState = toState; 228 } 229 updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true); 230 if (!mShouldDelayLauncherStateAnim) { 231 if (toState == LauncherState.NORMAL) { 232 TaskbarActivityContext activity = mControllers.taskbarActivityContext; 233 boolean isPinnedTaskbarAndNotInDesktopMode = 234 !activity.isInDesktopMode() && activity.isPinnedTaskbar(); 235 applyState(QuickstepTransitionManager.getTaskbarToHomeDuration( 236 isPinnedTaskbarAndNotInDesktopMode)); 237 } else { 238 applyState(); 239 } 240 } 241 } 242 243 @Override 244 public void onStateTransitionComplete(LauncherState finalState) { 245 mLauncherState = finalState; 246 updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, false); 247 applyState(); 248 updateOverviewDragState(finalState); 249 } 250 }; 251 252 private final StateManager.StateListener<RecentsState> mRecentsStateListener = 253 new StateManager.StateListener<>() { 254 255 @Override 256 public void onStateTransitionStart(RecentsState toState) { 257 mStateListener.onStateTransitionStart(toLauncherState(toState)); 258 } 259 260 @Override 261 public void onStateTransitionComplete(RecentsState finalState) { 262 mStateListener.onStateTransitionComplete(toLauncherState(finalState)); 263 } 264 }; 265 266 /** 267 * Callback for when launcher state transition completes after user swipes to home. 268 * @param finalState The final state of the transition. 269 */ onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState)270 public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) { 271 // TODO(b/279514548) Cleans up bad state that can occur when user interacts with 272 // taskbar on top of transparent activity. 273 if ((finalState == LauncherState.NORMAL) 274 && mLauncher.hasBeenResumed()) { 275 updateStateForFlag(FLAG_VISIBLE, true); 276 applyState(); 277 } 278 } 279 280 /** Initializes the controller instance, and applies the initial state immediately. */ init(TaskbarControllers controllers, QuickstepLauncher launcher, @SystemUiStateFlags long sysuiStateFlags)281 public void init(TaskbarControllers controllers, QuickstepLauncher launcher, 282 @SystemUiStateFlags long sysuiStateFlags) { 283 mCanSyncViews = false; 284 285 mControllers = controllers; 286 mLauncher = launcher; 287 288 mIsQsbInline = mLauncher.getDeviceProfile().isQsbInline; 289 290 mTaskbarBackgroundAlpha = mControllers.taskbarDragLayerController 291 .getTaskbarBackgroundAlpha(); 292 mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha(); 293 mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness(); 294 mTaskbarAlphaForHome = mControllers.taskbarViewController 295 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME); 296 297 resetIconAlignment(); 298 299 if (!mControllers.taskbarActivityContext.isPhoneMode()) { 300 mLauncher.getStateManager().addStateListener(mStateListener); 301 runForRecentsWindowManager(recentsWindowManager -> 302 recentsWindowManager.getStateManager().addStateListener(mRecentsStateListener)); 303 } 304 mLauncherState = launcher.getStateManager().getState(); 305 updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false); 306 307 applyState(0); 308 309 mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode(); 310 mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 311 updateOverviewDragState(mLauncherState); 312 } 313 onDestroy()314 public void onDestroy() { 315 mIsDestroyed = true; 316 mCanSyncViews = false; 317 318 if (mRecentsAnimationCallbacks != null) { 319 mRecentsAnimationCallbacks.removeListener(mTaskBarRecentsAnimationListener); 320 mRecentsAnimationCallbacks = null; 321 } 322 323 mIconAlignment.finishAnimation(); 324 325 mLauncher.getHotseat().setIconsAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 326 mLauncher.getStateManager().removeStateListener(mStateListener); 327 runForRecentsWindowManager(recentsWindowManager -> 328 recentsWindowManager.getStateManager().removeStateListener(mRecentsStateListener)); 329 330 mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode(); 331 mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener); 332 } 333 334 /** 335 * Creates a transition animation to the launcher activity. 336 * 337 * Warning: the resulting animation must be played, since this method has side effects on this 338 * controller's state. 339 */ createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)340 public Animator createAnimToLauncher(@NonNull LauncherState toState, 341 @NonNull RecentsAnimationCallbacks callbacks, long duration) { 342 // If going to overview, stash the task bar 343 // If going home, align the icons to hotseat 344 AnimatorSet animatorSet = new AnimatorSet(); 345 mRecentsAnimationCallbacks = callbacks; 346 347 // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly. 348 TaskbarStashController stashController = mControllers.taskbarStashController; 349 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, 350 toState.isTaskbarStashed(mLauncher)); 351 if (DEBUG) { 352 Log.d(TAG, "createAnimToLauncher - FLAG_IN_APP: " + false); 353 } 354 stashController.updateStateForFlag(FLAG_IN_APP, false); 355 356 updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true); 357 mLauncherState = toState; 358 animatorSet.play(stashController.createApplyStateAnimator(duration)); 359 animatorSet.play(applyState(duration, false)); 360 361 if (mTaskBarRecentsAnimationListener != null) { 362 mTaskBarRecentsAnimationListener.endGestureStateOverride( 363 !mLauncher.isInState(LauncherState.OVERVIEW), false /*canceled*/); 364 } 365 mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks); 366 callbacks.addListener(mTaskBarRecentsAnimationListener); 367 RecentsView recentsView = mControllers.uiController.getRecentsView(); 368 if (recentsView != null) { 369 recentsView.setTaskLaunchListener(() -> mTaskBarRecentsAnimationListener 370 .endGestureStateOverride(true, false /*canceled*/)); 371 recentsView.setTaskLaunchCancelledRunnable(() -> { 372 updateStateForUserFinishedToApp(false /* finishedToApp */); 373 }); 374 } 375 376 return animatorSet; 377 } 378 isAnimatingToLauncher()379 public boolean isAnimatingToLauncher() { 380 return mIsAnimatingToLauncher; 381 } 382 setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)383 public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) { 384 if (!shouldDelayLauncherStateAnim && mShouldDelayLauncherStateAnim) { 385 // Animate the animation we have delayed immediately. This is usually triggered when 386 // the user has released their finger. 387 applyState(); 388 } 389 mShouldDelayLauncherStateAnim = shouldDelayLauncherStateAnim; 390 } 391 392 /** Will make the next onRecentsAnimationFinished() a no-op. */ setSkipNextRecentsAnimEnd()393 public void setSkipNextRecentsAnimEnd() { 394 mSkipNextRecentsAnimEnd = true; 395 } 396 397 /** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */ updateStateForSysuiFlags(@ystemUiStateFlags long systemUiStateFlags)398 public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) { 399 updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true); 400 } 401 updateStateForSysuiFlags(@ystemUiStateFlags long systemUiStateFlags, boolean applyState)402 private void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags, 403 boolean applyState) { 404 final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE); 405 final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE); 406 407 updateStateForFlag(FLAG_AWAKE, currIsAwake); 408 if (prevIsAwake != currIsAwake) { 409 // The screen is switching between on/off. When turning off, capture whether the 410 // launcher is active and memoize this state. 411 updateStateForFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 412 prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE)); 413 } 414 415 updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags)); 416 417 updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden(systemUiStateFlags)); 418 419 if (applyState) { 420 applyState(); 421 } 422 } 423 424 /** 425 * Updates overview drag state on various controllers based on {@link #mLauncherState}. 426 * 427 * @param launcherState The current state launcher is in 428 */ updateOverviewDragState(LauncherState launcherState)429 private void updateOverviewDragState(LauncherState launcherState) { 430 boolean disallowLongClick = mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher; 431 com.android.launcher3.taskbar.Utilities.setOverviewDragState( 432 mControllers, launcherState.disallowTaskbarGlobalDrag(), 433 disallowLongClick, launcherState.allowTaskbarInitialSplitSelection()); 434 } 435 436 /** 437 * Updates the proper flag to change the state of the task bar. 438 * 439 * Note that this only updates the flag. {@link #applyState()} needs to be called separately. 440 * 441 * @param flag The flag to update. 442 * @param enabled Whether to enable the flag 443 */ updateStateForFlag(int flag, boolean enabled)444 public void updateStateForFlag(int flag, boolean enabled) { 445 if (enabled) { 446 mState |= flag; 447 } else { 448 mState &= ~flag; 449 } 450 } 451 hasAnyFlag(long flagMask)452 private boolean hasAnyFlag(long flagMask) { 453 return hasAnyFlag(mState, flagMask); 454 } 455 hasAnyFlag(long flags, long flagMask)456 private boolean hasAnyFlag(long flags, long flagMask) { 457 return (flags & flagMask) != 0; 458 } 459 applyState()460 public void applyState() { 461 applyState(mControllers.taskbarStashController.getStashDuration()); 462 } 463 applyState(long duration)464 public void applyState(long duration) { 465 applyState(duration, true); 466 } 467 applyState(long duration, boolean start)468 public Animator applyState(long duration, boolean start) { 469 if (mIsDestroyed || mControllers.taskbarActivityContext.isPhoneMode()) { 470 return null; 471 } 472 Animator animator = null; 473 if (mPrevState == null || mPrevState != mState) { 474 // If this is our initial state, treat all flags as changed. 475 int changedFlags = mPrevState == null ? FLAGS_ALL : mPrevState ^ mState; 476 477 if (DEBUG) { 478 String stateString; 479 if (mPrevState == null) { 480 stateString = getStateString(mState) + "(initial update)"; 481 } else { 482 stateString = formatFlagChange(mState, mPrevState, 483 TaskbarLauncherStateController::getStateString); 484 } 485 Log.d(TAG, "applyState: " + stateString 486 + ", duration: " + duration 487 + ", start: " + start); 488 } 489 mPrevState = mState; 490 animator = onStateChangeApplied(changedFlags, duration, start); 491 } 492 return animator; 493 } 494 onStateChangeApplied(int changedFlags, long duration, boolean start)495 private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) { 496 final boolean isInLauncher = isInLauncher(); 497 final boolean isInOverview = mControllers.uiController.isInOverviewUi(); 498 final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat(); 499 final float toAlignment = isIconAlignedWithHotseat ? 1 : 0; 500 boolean handleOpenFloatingViews = false; 501 boolean isPinnedTaskbar = 502 mControllers.taskbarActivityContext.isPinnedTaskbar(); 503 if (DEBUG) { 504 Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher 505 + ", mLauncherState: " + mLauncherState 506 + ", toAlignment: " + toAlignment); 507 } 508 mControllers.bubbleControllers.ifPresent(controllers -> { 509 // Show the bubble bar when on launcher home (hotseat icons visible) or in overview 510 boolean onOverview = isInLauncher && mLauncherState == LauncherState.OVERVIEW; 511 boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible( 512 mLauncher, HOTSEAT_ICONS); 513 BubbleLauncherState state = onOverview 514 ? BubbleLauncherState.OVERVIEW 515 : hotseatIconsVisible 516 ? BubbleLauncherState.HOME 517 : BubbleLauncherState.IN_APP; 518 controllers.bubbleStashController.setLauncherState(state); 519 }); 520 521 TaskbarStashController stashController = mControllers.taskbarStashController; 522 stashController.updateStateForFlag(FLAG_IN_OVERVIEW, 523 mLauncherState == LauncherState.OVERVIEW); 524 525 AnimatorSet animatorSet = new AnimatorSet(); 526 527 if (hasAnyFlag(changedFlags, FLAG_LAUNCHER_IN_STATE_TRANSITION)) { 528 boolean launcherTransitionCompleted = !hasAnyFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION); 529 playStateTransitionAnim(animatorSet, duration, launcherTransitionCompleted); 530 531 if (launcherTransitionCompleted 532 && mLauncherState == LauncherState.QUICK_SWITCH_FROM_HOME) { 533 // We're about to be paused, set immediately to ensure seamless handoff. 534 updateStateForFlag(FLAG_VISIBLE, false); 535 applyState(0 /* duration */); 536 } 537 if (mLauncherState == LauncherState.NORMAL) { 538 // We're changing state to home, should close open popups e.g. Taskbar AllApps 539 handleOpenFloatingViews = true; 540 } 541 if (mLauncherState == LauncherState.OVERVIEW 542 && !mControllers.taskbarActivityContext.isPhoneMode()) { 543 // Calling to update the insets in TaskbarInsetController#updateInsetsTouchability 544 mControllers.taskbarActivityContext.notifyUpdateLayoutParams(); 545 } 546 } 547 548 if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) { 549 animatorSet.addListener(new AnimatorListenerAdapter() { 550 @Override 551 public void onAnimationStart(Animator animation) { 552 mIsAnimatingToLauncher = isInLauncher; 553 554 if (DEBUG) { 555 Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher); 556 } 557 stashController.updateStateForFlag(FLAG_IN_APP, !isInLauncher); 558 stashController.applyState(duration); 559 } 560 561 @Override 562 public void onAnimationEnd(Animator animation) { 563 mIsAnimatingToLauncher = false; 564 } 565 }); 566 567 // Handle closing open popups when going home/overview 568 handleOpenFloatingViews = true; 569 } else { 570 stashController.applyState(); 571 } 572 573 if (handleOpenFloatingViews && isInLauncher) { 574 AbstractFloatingView.closeAllOpenViews(mControllers.taskbarActivityContext); 575 } 576 577 if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { 578 // Take note of the current time, as the taskbar is made visible again. 579 mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime(); 580 } 581 582 boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN); 583 float taskbarAlpha = isHidden ? 0 : 1; 584 if (mTaskbarAlpha.isAnimating() || mTaskbarAlpha.value != taskbarAlpha) { 585 Animator taskbarVisibility = mTaskbarAlpha.animateToValue(taskbarAlpha); 586 587 taskbarVisibility.setDuration(duration); 588 if (isHidden) { 589 // Stash the transient taskbar once the taskbar is not visible. This reduces 590 // visual noise when unlocking the device afterwards. 591 animatorSet.addListener(new AnimatorListenerAdapter() { 592 @Override 593 public void onAnimationEnd(Animator animation) { 594 TaskbarStashController stashController = 595 mControllers.taskbarStashController; 596 stashController.updateAndAnimateTransientTaskbar( 597 /* stash */ true, /* bubblesShouldFollow */ true); 598 } 599 }); 600 } else { 601 // delay the fade in animation a bit to reduce visual noise when waking up a device 602 // with a fingerprint reader. This should only be done when the device was woken 603 // up via fingerprint reader, however since this information is currently not 604 // available, opting to always delay the fade-in a bit. 605 long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() 606 - mLastRemoveTaskbarHiddenTimeMs; 607 taskbarVisibility.setStartDelay( 608 Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs)); 609 } 610 animatorSet.play(taskbarVisibility); 611 } 612 613 float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1; 614 AnimatedFloat taskbarBgOffset = 615 mControllers.taskbarDragLayerController.getTaskbarBackgroundOffset(); 616 boolean showTaskbar = shouldShowTaskbar(mControllers.taskbarActivityContext, isInLauncher, 617 isInOverview); 618 float taskbarBgOffsetEnd = showTaskbar ? 0f : 1f; 619 float taskbarBgOffsetStart = showTaskbar ? 1f : 0f; 620 621 // Don't animate if background has reached desired value. 622 if (mTaskbarBackgroundAlpha.isAnimating() 623 || mTaskbarBackgroundAlpha.value != backgroundAlpha 624 || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart) 625 || taskbarBgOffset.value != taskbarBgOffsetEnd) { 626 mTaskbarBackgroundAlpha.cancelAnimation(); 627 if (DEBUG) { 628 Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - " 629 + mTaskbarBackgroundAlpha.value 630 + " -> " + backgroundAlpha + ": " + duration); 631 } 632 633 boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat; 634 boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat; 635 boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat; 636 // When Hotseat icons are not on top don't change duration or add start delay. 637 // This will keep the duration in sync for icon alignment and background fade in/out. 638 // For example, launching app from launcher all apps. 639 boolean isHotseatIconOnTopWhenAligned = 640 mControllers.uiController.isHotseatIconOnTopWhenAligned(); 641 642 float startDelay = 0; 643 // We want to delay the background from fading in so that the icons have time to move 644 // into the bounds of the background before it appears. 645 if (isInLauncherIconNotAligned) { 646 startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 647 } else if (notInLauncherIconNotAligned && isHotseatIconOnTopWhenAligned) { 648 startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT; 649 } 650 float newDuration = duration - startDelay; 651 if (isInLauncherIconIsAligned && isHotseatIconOnTopWhenAligned) { 652 // Make the background fade out faster so that it is gone by the time the 653 // icons move outside of the bounds of the background. 654 newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT; 655 } 656 Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha.animateToValue( 657 backgroundAlpha); 658 if (isPinnedTaskbar) { 659 setupPinnedTaskbarAnimation(animatorSet, showTaskbar, taskbarBgOffset, 660 taskbarBgOffsetStart, taskbarBgOffsetEnd, duration, taskbarBackgroundAlpha); 661 } else { 662 taskbarBackgroundAlpha.setDuration((long) newDuration); 663 taskbarBackgroundAlpha.setStartDelay((long) startDelay); 664 } 665 animatorSet.play(taskbarBackgroundAlpha); 666 } 667 668 float cornerRoundness = isInLauncher ? 0 : 1; 669 670 if (mControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview( 671 mControllers.taskbarActivityContext.getDisplayId()) 672 && mControllers.getSharedState() != null) { 673 cornerRoundness = 674 mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness( 675 mControllers.getSharedState().showCornerRadiusInDesktopMode); 676 } 677 678 // Don't animate if corner roundness has reached desired value. 679 if (mTaskbarCornerRoundness.isAnimating() 680 || mTaskbarCornerRoundness.value != cornerRoundness) { 681 mTaskbarCornerRoundness.cancelAnimation(); 682 if (DEBUG) { 683 Log.d(TAG, "onStateChangeApplied - taskbarCornerRoundness - " 684 + mTaskbarCornerRoundness.value 685 + " -> " + cornerRoundness + ": " + duration); 686 } 687 animatorSet.play(mTaskbarCornerRoundness.animateToValue(cornerRoundness)); 688 } 689 690 // Keep isUnlockTransition in sync with its counterpart in 691 // TaskbarStashController#createAnimToIsStashed. 692 boolean isUnlockTransition = 693 hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED); 694 if (isUnlockTransition) { 695 // the launcher might not be resumed at the time the device is considered 696 // unlocked (when the keyguard goes away), but possibly shortly afterwards. 697 // To play the unlock transition at the time the unstash animation actually happens, 698 // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS. 699 mLastUnlockTransitionTimeout = 700 SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS; 701 } 702 boolean isInUnlockTimeout = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout; 703 if (isUnlockTransition || isInUnlockTimeout) { 704 // When transitioning to unlocked, ensure the hotseat is fully visible from the 705 // beginning. The hotseat itself is animated by LauncherUnlockAnimationController. 706 mIconAlignment.cancelAnimation(); 707 // updateValue ensures onIconAlignmentRatioChanged will be called if there is an actual 708 // change in value 709 mIconAlignment.updateValue(toAlignment); 710 711 // Make sure FLAG_IN_APP is set when launching applications from keyguard. 712 if (!isInLauncher) { 713 mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true); 714 mControllers.taskbarStashController.applyState(0); 715 } 716 } else if (mIconAlignment.isAnimatingToValue(toAlignment) 717 || mIconAlignment.isSettledOnValue(toAlignment)) { 718 // Already at desired value, but make sure we run the callback at the end. 719 animatorSet.addListener(AnimatorListeners.forEndCallback(() -> { 720 if (!mIconAlignment.isAnimating()) { 721 onIconAlignmentRatioChanged(); 722 } 723 })); 724 } else { 725 mIconAlignment.cancelAnimation(); 726 ObjectAnimator iconAlignAnim = mIconAlignment 727 .animateToValue(toAlignment) 728 .setDuration(duration); 729 if (DEBUG) { 730 Log.d(TAG, "onStateChangeApplied - iconAlignment - " 731 + mIconAlignment.value 732 + " -> " + toAlignment + ": " + duration); 733 } 734 if (!isPinnedTaskbar) { 735 if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) { 736 iconAlignAnim.setInterpolator(FINAL_FRAME); 737 } else { 738 animatorSet.play(iconAlignAnim); 739 } 740 } 741 } 742 743 Interpolator interpolator = enableScalingRevealHomeAnimation() && !isPinnedTaskbar 744 ? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED; 745 746 animatorSet.setInterpolator(interpolator); 747 748 if (start) { 749 animatorSet.start(); 750 } 751 return animatorSet; 752 } 753 shouldShowTaskbar(TaskbarActivityContext activityContext, boolean isInLauncher, boolean isInOverview)754 private static boolean shouldShowTaskbar(TaskbarActivityContext activityContext, 755 boolean isInLauncher, boolean isInOverview) { 756 if (activityContext.showDesktopTaskbarForFreeformDisplay()) { 757 return true; 758 } 759 760 if (activityContext.showLockedTaskbarOnHome() && isInLauncher) { 761 return true; 762 } 763 return !isInLauncher || isInOverview; 764 } 765 setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar, AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd, long duration, Animator taskbarBackgroundAlpha)766 private void setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar, 767 AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd, 768 long duration, Animator taskbarBackgroundAlpha) { 769 float targetAlpha = !showTaskbar ? 1 : 0; 770 mLauncher.getHotseat().setIconsAlpha(targetAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 771 if (mIsQsbInline) { 772 mLauncher.getHotseat().setQsbAlpha(targetAlpha, 773 ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 774 } 775 776 if ((taskbarBgOffset.value != taskbarBgOffsetEnd && !taskbarBgOffset.isAnimating()) 777 || taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)) { 778 taskbarBgOffset.cancelAnimation(); 779 Animator taskbarIconAlpha = mTaskbarAlphaForHome.animateToValue( 780 showTaskbar ? 1f : 0f); 781 AnimatedFloat taskbarIconTranslationYForHome = 782 mControllers.taskbarViewController.mTaskbarIconTranslationYForHome; 783 ObjectAnimator taskbarBackgroundOffset = taskbarBgOffset.animateToValue( 784 taskbarBgOffsetStart, 785 taskbarBgOffsetEnd); 786 ObjectAnimator taskbarIconsYTranslation = null; 787 float taskbarHeight = 788 mControllers.taskbarActivityContext.getDeviceProfile().taskbarHeight; 789 if (showTaskbar) { 790 taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue( 791 taskbarHeight, 0); 792 } else { 793 taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(0, 794 taskbarHeight); 795 } 796 797 taskbarIconAlpha.setDuration(duration); 798 taskbarIconsYTranslation.setDuration(duration); 799 taskbarBackgroundOffset.setDuration(duration); 800 801 animatorSet.play(taskbarIconAlpha); 802 animatorSet.play(taskbarIconsYTranslation); 803 animatorSet.play(taskbarBackgroundOffset); 804 } 805 taskbarBackgroundAlpha.setInterpolator(showTaskbar ? INSTANT : FINAL_FRAME); 806 taskbarBackgroundAlpha.setDuration(duration); 807 } 808 809 /** 810 * Whether the taskbar is aligned with the hotseat in the current/target launcher state. 811 * 812 * This refers to the intended state - a transition to this state might be in progress. 813 */ isTaskbarAlignedWithHotseat()814 public boolean isTaskbarAlignedWithHotseat() { 815 if (mControllers.taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) { 816 return false; 817 } 818 819 if (mControllers.taskbarActivityContext.showLockedTaskbarOnHome() && isInLauncher()) { 820 return false; 821 } 822 823 return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher); 824 } 825 826 /** 827 * Returns if icons should be aligned to hotseat in the current transition 828 */ isIconAlignedWithHotseat()829 public boolean isIconAlignedWithHotseat() { 830 if (isInLauncher()) { 831 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 832 boolean willStashVisually = isInStashedState 833 && mControllers.taskbarStashController.supportsVisualStashing(); 834 boolean isTaskbarAlignedWithHotseat = isTaskbarAlignedWithHotseat(); 835 return isTaskbarAlignedWithHotseat && !willStashVisually; 836 } else { 837 return false; 838 } 839 } 840 841 /** 842 * Returns if the current Launcher state has hotseat on top of other elemnets. 843 */ isInHotseatOnTopStates()844 public boolean isInHotseatOnTopStates() { 845 return mLauncherState != LauncherState.ALL_APPS 846 && !mLauncher.getWorkspace().isOverlayShown(); 847 } 848 isInOverviewUi()849 boolean isInOverviewUi() { 850 return mLauncherState.isRecentsViewVisible; 851 } 852 853 /** 854 * Returns the current mLauncherState. Note that this could represent RecentsState as well, as 855 * we convert those to equivalent LauncherStates even if Launcher Activity is not actually in 856 * those states (for the case where the state is represented in a separate Window instead). 857 */ getLauncherState()858 public LauncherState getLauncherState() { 859 return mLauncherState; 860 } 861 playStateTransitionAnim(AnimatorSet animatorSet, long duration, boolean committed)862 private void playStateTransitionAnim(AnimatorSet animatorSet, long duration, 863 boolean committed) { 864 boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher); 865 TaskbarStashController stashController = mControllers.taskbarStashController; 866 stashController.updateStateForFlag(FLAG_IN_STASHED_LAUNCHER_STATE, isInStashedState); 867 Animator stashAnimator = stashController.createApplyStateAnimator(duration); 868 if (stashAnimator != null) { 869 stashAnimator.addListener(new AnimatorListenerAdapter() { 870 @Override 871 public void onAnimationEnd(Animator animation) { 872 if (isInStashedState && committed) { 873 // Reset hotseat alpha to default 874 mLauncher.getHotseat().setIconsAlpha(1, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 875 } 876 } 877 878 @Override 879 public void onAnimationStart(Animator animation) { 880 float hotseatIconsAlpha = mLauncher.getHotseat() 881 .getIconsAlpha(ALPHA_CHANNEL_TASKBAR_ALIGNMENT) 882 .getValue(); 883 if (hotseatIconsAlpha > 0) { 884 updateIconAlphaForHome(hotseatIconsAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT); 885 } 886 } 887 }); 888 animatorSet.play(stashAnimator); 889 } 890 891 // Translate back to 0 at a shorter or same duration as the icon alignment animation. 892 // This ensures there is no jump after switching to hotseat, e.g. when swiping up from 893 // overview to home. When not in app, we do duration / 2 just to make it feel snappier. 894 long resetDuration = mControllers.taskbarStashController.isInApp() 895 ? duration 896 : duration / 2; 897 if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration) 898 && (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) { 899 animatorSet.play(mControllers.taskbarTranslationController 900 .createAnimToResetTranslation(resetDuration)); 901 } 902 } 903 904 /** Whether the launcher is considered active. */ isInLauncher()905 private boolean isInLauncher() { 906 if (hasAnyFlag(FLAG_AWAKE)) { 907 return hasAnyFlag(FLAGS_LAUNCHER_ACTIVE); 908 } else { 909 return hasAnyFlag(FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE); 910 } 911 } 912 stashHotseat(boolean stash)913 protected void stashHotseat(boolean stash) { 914 // align taskbar with the hotseat icons before performing any animation 915 mControllers.taskbarViewController.setLauncherIconAlignment(/* alignmentRatio = */ 1, 916 mLauncher.getDeviceProfile()); 917 TaskbarStashController stashController = mControllers.taskbarStashController; 918 stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, stash); 919 Runnable swapHotseatWithTaskbar = new Runnable() { 920 @Override 921 public void run() { 922 updateIconAlphaForHome(stash ? 1 : 0, ALPHA_CHANNEL_TASKBAR_STASH); 923 } 924 }; 925 if (stash) { 926 stashController.applyState(); 927 // if we stashing the hotseat we need to immediately swap it with the animating taskbar 928 swapHotseatWithTaskbar.run(); 929 } else { 930 // if we revert stashing make swap after taskbar animation is complete 931 stashController.applyState(/* postApplyAction = */ swapHotseatWithTaskbar); 932 } 933 } 934 unStashHotseatInstantly()935 protected void unStashHotseatInstantly() { 936 TaskbarStashController stashController = mControllers.taskbarStashController; 937 stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, false); 938 stashController.applyState(/* duration = */ 0); 939 updateIconAlphaForHome(/* taskbarAlpha = */ 0, 940 ALPHA_CHANNEL_TASKBAR_STASH, /* updateTaskbarAlpha = */ false); 941 } 942 943 /** 944 * Resets and updates the icon alignment. 945 */ resetIconAlignment()946 protected void resetIconAlignment() { 947 mIconAlignment.finishAnimation(); 948 onIconAlignmentRatioChanged(); 949 } 950 onIconAlignmentRatioChanged()951 private void onIconAlignmentRatioChanged() { 952 float currentValue = mTaskbarAlphaForHome.getValue(); 953 boolean taskbarWillBeVisible = mIconAlignment.value < 1; 954 boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0) 955 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0); 956 957 mControllers.taskbarViewController.setLauncherIconAlignment( 958 mIconAlignment.value, mLauncher.getDeviceProfile()); 959 mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value); 960 // Switch taskbar and hotseat in last frame and if taskbar is not hidden for bubbles 961 boolean isHiddenForBubbles = mControllers.taskbarStashController.isHiddenForBubbles(); 962 updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0, ALPHA_CHANNEL_TASKBAR_ALIGNMENT, 963 /* updateTaskbarAlpha = */ !isHiddenForBubbles); 964 965 // Sync the first frame where we swap taskbar and hotseat. 966 if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) { 967 ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(), 968 mControllers.taskbarActivityContext.getDragLayer(), 969 () -> {}); 970 } 971 } 972 973 private void updateIconAlphaForHome(float taskbarAlpha, @HotseatQsbAlphaId int alphaChannel) { 974 updateIconAlphaForHome(taskbarAlpha, alphaChannel, /* updateTaskbarAlpha = */ true); 975 } 976 977 private void updateIconAlphaForHome(float taskbarAlpha, 978 @HotseatQsbAlphaId int alphaChannel, 979 boolean updateTaskbarAlpha) { 980 if (mIsDestroyed) { 981 return; 982 } 983 if (updateTaskbarAlpha) { 984 mTaskbarAlphaForHome.setValue(taskbarAlpha); 985 } 986 boolean hotseatVisible = taskbarAlpha == 0 987 || mControllers.taskbarActivityContext.isPhoneMode() 988 || (!mControllers.uiController.isHotseatIconOnTopWhenAligned() 989 && mIconAlignment.value > 0); 990 /* 991 * Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets 992 * should not be visible at the same time. 993 */ 994 float targetAlpha = hotseatVisible ? 1 : 0; 995 mLauncher.getHotseat().setIconsAlpha(targetAlpha, alphaChannel); 996 if (mIsQsbInline) { 997 mLauncher.getHotseat().setQsbAlpha(targetAlpha, alphaChannel); 998 } 999 } 1000 1001 /** Updates launcher home screen appearance accordingly to the bubble bar location. */ 1002 public void onBubbleBarLocationChanged(@Nullable BubbleBarLocation location, boolean animate) { 1003 mBubbleBarLocation = location; 1004 if (location == null) { 1005 // bubble bar is not present, hence no location, resetting the hotseat 1006 updateHotseatAndQsbTranslationX(/* targetValue = */ 0, animate); 1007 mBubbleBarLocation = null; 1008 return; 1009 } 1010 DeviceProfile deviceProfile = mLauncher.getDeviceProfile(); 1011 if (!deviceProfile.shouldAdjustHotseatOnNavBarLocationUpdate( 1012 mControllers.taskbarActivityContext)) { 1013 return; 1014 } 1015 boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources())); 1016 int targetX = deviceProfile 1017 .getHotseatTranslationXForNavBar(mLauncher, isBubblesOnLeft); 1018 updateHotseatAndQsbTranslationX(targetX, animate); 1019 } 1020 1021 /** Used to translate hotseat and QSB to make room for bubbles. */ 1022 private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) { 1023 // cancel existing animation 1024 if (mHotseatTranslationXAnimation != null) { 1025 mHotseatTranslationXAnimation.cancel(); 1026 mHotseatTranslationXAnimation = null; 1027 } 1028 Hotseat hotseat = mLauncher.getHotseat(); 1029 AnimatorSet translationXAnimation = new AnimatorSet(); 1030 MultiProperty iconsTranslationX = mLauncher.getHotseat() 1031 .getIconsTranslationX(Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT); 1032 if (animate) { 1033 translationXAnimation.playTogether(iconsTranslationX.animateToValue(targetValue)); 1034 } else { 1035 iconsTranslationX.setValue(targetValue); 1036 } 1037 float qsbTargetX = 0; 1038 if (mIsQsbInline) { 1039 qsbTargetX = targetValue; 1040 } 1041 MultiProperty qsbTranslationX = hotseat.getQsbTranslationX(); 1042 if (qsbTranslationX != null) { 1043 if (animate) { 1044 translationXAnimation.playTogether(qsbTranslationX.animateToValue(qsbTargetX)); 1045 } else { 1046 qsbTranslationX.setValue(qsbTargetX); 1047 } 1048 } 1049 if (!animate) { 1050 return; 1051 } 1052 mHotseatTranslationXAnimation = translationXAnimation; 1053 translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS); 1054 translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS); 1055 translationXAnimation.setInterpolator(Interpolators.EMPHASIZED); 1056 translationXAnimation.start(); 1057 } 1058 1059 private final class TaskBarRecentsAnimationListener implements 1060 RecentsAnimationCallbacks.RecentsAnimationListener { 1061 private final RecentsAnimationCallbacks mCallbacks; 1062 1063 TaskBarRecentsAnimationListener(RecentsAnimationCallbacks callbacks) { 1064 mCallbacks = callbacks; 1065 } 1066 1067 @Override 1068 public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) { 1069 boolean isInOverview = mLauncher.isInState(LauncherState.OVERVIEW); 1070 endGestureStateOverride(!isInOverview, true /*canceled*/); 1071 } 1072 1073 @Override 1074 public void onRecentsAnimationFinished(RecentsAnimationController controller) { 1075 endGestureStateOverride(!controller.getFinishTargetIsLauncher(), 1076 controller.getLauncherIsVisibleAtFinish(), false /*canceled*/); 1077 } 1078 1079 private void endGestureStateOverride(boolean finishedToApp, boolean canceled) { 1080 endGestureStateOverride(finishedToApp, finishedToApp, canceled); 1081 } 1082 1083 /** 1084 * Handles whatever cleanup is needed after the recents animation is completed. 1085 * NOTE: If {@link #mSkipNextRecentsAnimEnd} is set and we're coming from a non-cancelled 1086 * path, this will not call {@link #updateStateForUserFinishedToApp(boolean)} 1087 * 1088 * @param finishedToApp {@code true} if the recents animation finished to showing an app and 1089 * not workspace or overview 1090 * @param launcherIsVisible {code true} if launcher is visible at finish 1091 * @param canceled {@code true} if the recents animation was canceled instead of 1092 * finishing 1093 * to completion 1094 */ 1095 private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible, 1096 boolean canceled) { 1097 mCallbacks.removeListener(this); 1098 mTaskBarRecentsAnimationListener = null; 1099 RecentsView recentsView = mControllers.uiController.getRecentsView(); 1100 if (recentsView != null) { 1101 recentsView.setTaskLaunchListener(null); 1102 } 1103 1104 if (mSkipNextRecentsAnimEnd && !canceled) { 1105 mSkipNextRecentsAnimEnd = false; 1106 return; 1107 } 1108 updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible); 1109 } 1110 } 1111 1112 /** 1113 * @see #updateStateForUserFinishedToApp(boolean, boolean) 1114 */ 1115 private void updateStateForUserFinishedToApp(boolean finishedToApp) { 1116 updateStateForUserFinishedToApp(finishedToApp, !finishedToApp); 1117 } 1118 1119 /** 1120 * Updates the visible state immediately to ensure a seamless handoff. 1121 * 1122 * @param finishedToApp True iff user is in an app. 1123 * @param launcherIsVisible True iff launcher is still visible (ie. transparent app) 1124 */ 1125 private void updateStateForUserFinishedToApp(boolean finishedToApp, 1126 boolean launcherIsVisible) { 1127 // Update the visible state immediately to ensure a seamless handoff 1128 boolean launcherVisible = !finishedToApp || launcherIsVisible; 1129 updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false); 1130 updateStateForFlag(FLAG_VISIBLE, launcherVisible); 1131 applyState(); 1132 1133 TaskbarStashController controller = mControllers.taskbarStashController; 1134 if (DEBUG) { 1135 Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp); 1136 } 1137 controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible); 1138 controller.applyState(); 1139 } 1140 1141 /** 1142 * Helper function to run a callback on the RecentsWindowManager (if it exists). 1143 */ 1144 private void runForRecentsWindowManager(Consumer<RecentsWindowManager> callback) { 1145 if (RecentsWindowFlags.getEnableOverviewInWindow()) { 1146 final TaskbarActivityContext taskbarContext = mControllers.taskbarActivityContext; 1147 RecentsWindowManager recentsWindowManager = RecentsDisplayModel.getINSTANCE() 1148 .get(taskbarContext).getRecentsWindowManager(taskbarContext.getDisplayId()); 1149 if (recentsWindowManager != null) { 1150 callback.accept(recentsWindowManager); 1151 } 1152 } 1153 } 1154 1155 private static String getStateString(int flags) { 1156 StringJoiner result = new StringJoiner("|"); 1157 appendFlag(result, flags, FLAG_VISIBLE, "flag_visible"); 1158 appendFlag(result, flags, FLAG_TRANSITION_TO_VISIBLE, "transition_to_visible"); 1159 appendFlag(result, flags, FLAG_LAUNCHER_IN_STATE_TRANSITION, 1160 "launcher_in_state_transition"); 1161 appendFlag(result, flags, FLAG_AWAKE, "awake"); 1162 appendFlag(result, flags, FLAG_LAUNCHER_WAS_ACTIVE_WHILE_AWAKE, 1163 "was_active_while_awake"); 1164 appendFlag(result, flags, FLAG_DEVICE_LOCKED, "device_locked"); 1165 appendFlag(result, flags, FLAG_TASKBAR_HIDDEN, "taskbar_hidden"); 1166 return result.toString(); 1167 } 1168 1169 protected void dumpLogs(String prefix, PrintWriter pw) { 1170 pw.println(prefix + "TaskbarLauncherStateController:"); 1171 pw.println(String.format( 1172 "%s\tmIconAlignment=%.2f", 1173 prefix, 1174 mIconAlignment.value)); 1175 pw.println(String.format( 1176 "%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value)); 1177 pw.println(String.format( 1178 "%s\tmTaskbarAlphaForHome=%.2f", prefix, mTaskbarAlphaForHome.getValue())); 1179 pw.println(String.format("%s\tmPrevState=%s", prefix, 1180 mPrevState == null ? null : getStateString(mPrevState))); 1181 pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState))); 1182 pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState)); 1183 pw.println(String.format( 1184 "%s\tmIsAnimatingToLauncher=%b", 1185 prefix, 1186 mIsAnimatingToLauncher)); 1187 pw.println(String.format( 1188 "%s\tmShouldDelayLauncherStateAnim=%b", prefix, mShouldDelayLauncherStateAnim)); 1189 } 1190 } 1191