1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS; 19 20 import static com.android.app.animation.Interpolators.EMPHASIZED; 21 import static com.android.app.animation.Interpolators.FINAL_FRAME; 22 import static com.android.app.animation.Interpolators.INSTANT; 23 import static com.android.app.animation.Interpolators.LINEAR; 24 import static com.android.internal.jank.InteractionJankMonitor.Configuration; 25 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 26 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; 27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE; 28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW; 29 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 30 import static com.android.launcher3.util.FlagDebugUtils.appendFlag; 31 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange; 32 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR; 33 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; 34 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; 35 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; 36 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE; 37 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 38 39 import android.animation.Animator; 40 import android.animation.AnimatorListenerAdapter; 41 import android.animation.AnimatorSet; 42 import android.animation.ValueAnimator; 43 import android.app.RemoteAction; 44 import android.graphics.drawable.Icon; 45 import android.os.SystemClock; 46 import android.util.Log; 47 import android.view.InsetsController; 48 import android.view.View; 49 import android.view.ViewConfiguration; 50 import android.view.accessibility.AccessibilityManager; 51 import android.view.animation.Interpolator; 52 53 import androidx.annotation.IntDef; 54 import androidx.annotation.NonNull; 55 import androidx.annotation.Nullable; 56 import androidx.annotation.VisibleForTesting; 57 58 import com.android.internal.jank.InteractionJankMonitor; 59 import com.android.launcher3.Alarm; 60 import com.android.launcher3.DeviceProfile; 61 import com.android.launcher3.R; 62 import com.android.launcher3.anim.AnimatedFloat; 63 import com.android.launcher3.anim.AnimatorListeners; 64 import com.android.launcher3.statehandlers.DesktopVisibilityController; 65 import com.android.launcher3.util.DisplayController; 66 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; 67 import com.android.quickstep.LauncherActivityInterface; 68 import com.android.quickstep.SystemUiProxy; 69 import com.android.quickstep.util.SystemUiFlagUtils; 70 71 import java.io.PrintWriter; 72 import java.lang.annotation.Retention; 73 import java.lang.annotation.RetentionPolicy; 74 import java.util.StringJoiner; 75 import java.util.function.LongPredicate; 76 77 /** 78 * Coordinates between controllers such as TaskbarViewController and StashedHandleViewController to 79 * create a cohesive animation between stashed/unstashed states. 80 */ 81 public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController { 82 private static final String TAG = "TaskbarStashController"; 83 private static final boolean DEBUG = false; 84 85 public static final int FLAG_IN_APP = 1 << 0; 86 public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ... 87 public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity 88 public static final int FLAG_STASHED_IME = 1 << 3; // IME is visible 89 public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 4; 90 public static final int FLAG_STASHED_IN_TASKBAR_ALL_APPS = 1 << 5; // All apps is visible. 91 public static final int FLAG_IN_SETUP = 1 << 6; // In the Setup Wizard 92 public static final int FLAG_STASHED_SMALL_SCREEN = 1 << 7; // phone screen gesture nav, stashed 93 public static final int FLAG_STASHED_IN_APP_AUTO = 1 << 8; // Autohide (transient taskbar). 94 public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,... 95 public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ... 96 public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview 97 98 // If any of these flags are enabled, isInApp should return true. 99 private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP; 100 101 // If we're in an app and any of these flags are enabled, taskbar should be stashed. 102 private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_SYSUI 103 | FLAG_STASHED_IN_APP_SETUP | FLAG_STASHED_IN_TASKBAR_ALL_APPS 104 | FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO | FLAG_STASHED_IME; 105 106 // If we're in overview and any of these flags are enabled, taskbar should be stashed. 107 private static final int FLAGS_STASHED_IN_OVERVIEW = FLAG_STASHED_IME; 108 109 // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed 110 // height. This way the reported insets are consistent even during transitions out of the app. 111 // Currently any flag that causes us to stash in an app is included, except for IME or All Apps 112 // since those cover the underlying app anyway and thus the app shouldn't change insets. 113 private static final int FLAGS_REPORT_STASHED_INSETS_TO_APP = FLAGS_STASHED_IN_APP 114 & ~FLAG_STASHED_IME & ~FLAG_STASHED_IN_TASKBAR_ALL_APPS & ~FLAG_STASHED_IN_APP_SYSUI; 115 116 // If any of these flags are enabled, the taskbar must be stashed. 117 private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED 118 | FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN; 119 120 /** 121 * How long to stash/unstash when manually invoked via long press. 122 * 123 * Use {@link #getStashDuration()} to query duration 124 */ 125 private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE; 126 127 /** 128 * How long to stash/unstash transient taskbar. 129 * 130 * Use {@link #getStashDuration()} to query duration. 131 */ 132 private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417; 133 134 /** 135 * How long to stash/unstash when keyboard is appearing/disappearing. 136 */ 137 private static final long TASKBAR_STASH_DURATION_FOR_IME = 80; 138 139 /** 140 * The scale TaskbarView animates to when being stashed. 141 */ 142 protected static final float STASHED_TASKBAR_SCALE = 0.5f; 143 144 /** 145 * How long the hint animation plays, starting on motion down. 146 */ 147 private static final long TASKBAR_HINT_STASH_DURATION = 148 ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT; 149 150 /** 151 * How long to delay the icon/stash handle alpha. 152 */ 153 private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33; 154 155 /** 156 * How long the icon/stash handle alpha animation plays. 157 */ 158 private static final long TASKBAR_STASH_ALPHA_DURATION = 50; 159 160 /** 161 * How long to delay the icon/stash handle alpha for the home to app taskbar animation. 162 */ 163 private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66; 164 165 /** 166 * The scale that the stashed handle animates to when hinting towards the unstashed state. 167 */ 168 private static final float UNSTASHED_TASKBAR_HANDLE_HINT_SCALE = 1.1f; 169 170 /** 171 * Whether taskbar should be stashed out of the box. 172 */ 173 private static final boolean DEFAULT_STASHED_PREF = false; 174 175 // Auto stashes when user has not interacted with the Taskbar after X ms. 176 private static final long NO_TOUCH_TIMEOUT_TO_STASH_MS = 5000; 177 178 // Duration for which an unlock event is considered "current", as other events are received 179 // asynchronously. 180 private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200; 181 182 /** 183 * The default stash animation, morphing the taskbar into the navbar. 184 */ 185 private static final int TRANSITION_DEFAULT = 0; 186 /** 187 * Transitioning from launcher to app. Same as TRANSITION_DEFAULT, differs in internal 188 * animation timings. 189 */ 190 private static final int TRANSITION_HOME_TO_APP = 1; 191 /** 192 * Fading the navbar in and out, where the taskbar jumpcuts in and out at the very begin/end of 193 * the transition. Used to transition between the hotseat and navbar` without the stash/unstash 194 * transition. 195 */ 196 private static final int TRANSITION_HANDLE_FADE = 2; 197 /** 198 * Same as TRANSITION_DEFAULT, but exclusively used during an "navbar unstash to hotseat 199 * animation" bound to the progress of a swipe gesture. It differs from TRANSITION_DEFAULT 200 * by not scaling the height of the taskbar background. 201 */ 202 private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3; 203 204 @Retention(RetentionPolicy.SOURCE) 205 @IntDef(value = { 206 TRANSITION_DEFAULT, 207 TRANSITION_HOME_TO_APP, 208 TRANSITION_HANDLE_FADE, 209 TRANSITION_UNSTASH_SUW_MANUAL, 210 }) 211 private @interface StashAnimation { 212 } 213 214 private final TaskbarActivityContext mActivity; 215 private final int mStashedHeight; 216 private final int mUnstashedHeight; 217 private final SystemUiProxy mSystemUiProxy; 218 219 // Initialized in init. 220 private TaskbarControllers mControllers; 221 // Taskbar background properties. 222 private AnimatedFloat mTaskbarBackgroundOffset; 223 private AnimatedFloat mTaskbarImeBgAlpha; 224 private MultiProperty mTaskbarBackgroundAlphaForStash; 225 // TaskbarView icon properties. 226 private MultiProperty mIconAlphaForStash; 227 private AnimatedFloat mIconScaleForStash; 228 private AnimatedFloat mIconTranslationYForStash; 229 // Stashed handle properties. 230 private MultiProperty mTaskbarStashedHandleAlpha; 231 private AnimatedFloat mTaskbarStashedHandleHintScale; 232 private final AccessibilityManager mAccessibilityManager; 233 234 /** Whether we are currently visually stashed (might change based on launcher state). */ 235 private boolean mIsStashed = false; 236 private long mState; 237 238 private @Nullable AnimatorSet mAnimator; 239 private boolean mIsSystemGestureInProgress; 240 private boolean mIsImeShowing; 241 private boolean mIsImeSwitcherShowing; 242 243 private final Alarm mTimeoutAlarm = new Alarm(); 244 private boolean mEnableBlockingTimeoutDuringTests = false; 245 246 private Animator mTaskbarBackgroundAlphaAnimator; 247 private long mTaskbarBackgroundDuration; 248 private boolean mIsGoingHome; 249 250 // Evaluate whether the handle should be stashed 251 private final LongPredicate mIsStashedPredicate = flags -> { 252 boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP); 253 boolean stashedInApp = hasAnyFlag(flags, FLAGS_STASHED_IN_APP); 254 boolean stashedLauncherState = hasAnyFlag(flags, FLAG_IN_STASHED_LAUNCHER_STATE); 255 boolean inOverview = hasAnyFlag(flags, FLAG_IN_OVERVIEW); 256 boolean stashedInOverview = hasAnyFlag(flags, FLAGS_STASHED_IN_OVERVIEW); 257 boolean forceStashed = hasAnyFlag(flags, FLAGS_FORCE_STASHED); 258 return (inApp && stashedInApp) 259 || (!inApp && stashedLauncherState) 260 || (inOverview && stashedInOverview) 261 || forceStashed; 262 }; 263 private final StatePropertyHolder mStatePropertyHolder = new StatePropertyHolder( 264 mIsStashedPredicate); 265 266 private boolean mIsTaskbarSystemActionRegistered = false; 267 private TaskbarSharedState mTaskbarSharedState; 268 TaskbarStashController(TaskbarActivityContext activity)269 public TaskbarStashController(TaskbarActivityContext activity) { 270 mActivity = activity; 271 mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity); 272 mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class); 273 274 mTaskbarBackgroundDuration = 275 activity.getResources().getInteger(R.integer.taskbar_background_duration); 276 if (mActivity.isPhoneMode()) { 277 mUnstashedHeight = mActivity.getResources().getDimensionPixelSize( 278 R.dimen.taskbar_phone_size); 279 mStashedHeight = mActivity.getResources().getDimensionPixelSize( 280 R.dimen.taskbar_stashed_size); 281 } else { 282 mUnstashedHeight = mActivity.getDeviceProfile().taskbarHeight; 283 mStashedHeight = mActivity.getDeviceProfile().stashedTaskbarHeight; 284 } 285 } 286 287 /** 288 * Show Taskbar upon receiving broadcast 289 */ showTaskbarFromBroadcast()290 public void showTaskbarFromBroadcast() { 291 // If user is in middle of taskbar education handle go to next step of education 292 if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) { 293 mControllers.taskbarEduTooltipController.hide(); 294 mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu(); 295 } 296 updateAndAnimateTransientTaskbar(false); 297 } 298 299 /** 300 * Initializes the controller 301 */ init( TaskbarControllers controllers, boolean setupUIVisible, TaskbarSharedState sharedState)302 public void init( 303 TaskbarControllers controllers, 304 boolean setupUIVisible, 305 TaskbarSharedState sharedState) { 306 mControllers = controllers; 307 mTaskbarSharedState = sharedState; 308 309 TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController; 310 mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset(); 311 mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar(); 312 mTaskbarBackgroundAlphaForStash = dragLayerController.getBackgroundRendererAlphaForStash(); 313 314 TaskbarViewController taskbarViewController = controllers.taskbarViewController; 315 mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get( 316 TaskbarViewController.ALPHA_INDEX_STASH); 317 mIconScaleForStash = taskbarViewController.getTaskbarIconScaleForStash(); 318 mIconTranslationYForStash = taskbarViewController.getTaskbarIconTranslationYForStash(); 319 320 StashedHandleViewController stashedHandleController = 321 controllers.stashedHandleViewController; 322 mTaskbarStashedHandleAlpha = stashedHandleController.getStashedHandleAlpha().get( 323 StashedHandleViewController.ALPHA_INDEX_STASHED); 324 mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale(); 325 326 boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity); 327 boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible; 328 boolean isStashedInAppAuto = 329 isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned(); 330 331 if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { 332 isStashedInAppAuto = isStashedInAppAuto && mTaskbarSharedState.taskbarWasStashedAuto; 333 } 334 updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, isStashedInAppAuto); 335 updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, isInSetup); 336 updateStateForFlag(FLAG_IN_SETUP, isInSetup); 337 updateStateForFlag(FLAG_STASHED_SMALL_SCREEN, mActivity.isPhoneGestureNavMode()); 338 // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell 339 // us that we're paused until a bit later. This avoids flickering upon recreating taskbar. 340 updateStateForFlag(FLAG_IN_APP, true); 341 applyState(/* duration = */ 0); 342 if (mTaskbarSharedState.getTaskbarWasPinned() 343 || !mTaskbarSharedState.taskbarWasStashedAuto) { 344 tryStartTaskbarTimeout(); 345 } 346 notifyStashChange(/* visible */ false, /* stashed */ isStashedInApp()); 347 } 348 349 /** 350 * Returns whether the taskbar can visually stash into a handle based on the current device 351 * state. 352 */ supportsVisualStashing()353 public boolean supportsVisualStashing() { 354 return !mActivity.isThreeButtonNav() && mControllers.uiController.supportsVisualStashing(); 355 } 356 357 /** 358 * Enables the auto timeout for taskbar stashing. This method should only be used for taskbar 359 * testing. 360 */ 361 @VisibleForTesting enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout)362 public void enableBlockingTimeoutDuringTests(boolean enableBlockingTimeout) { 363 mEnableBlockingTimeoutDuringTests = enableBlockingTimeout; 364 } 365 366 /** 367 * Sets the flag indicating setup UI is visible 368 */ setSetupUIVisible(boolean isVisible)369 protected void setSetupUIVisible(boolean isVisible) { 370 boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete(); 371 updateStateForFlag(FLAG_IN_SETUP, hideTaskbar); 372 updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar); 373 applyState(hideTaskbar ? 0 : getStashDuration()); 374 } 375 376 /** 377 * Returns how long the stash/unstash animation should play. 378 */ getStashDuration()379 public long getStashDuration() { 380 return DisplayController.isTransientTaskbar(mActivity) 381 ? TRANSIENT_TASKBAR_STASH_DURATION 382 : TASKBAR_STASH_DURATION; 383 } 384 385 /** 386 * Returns whether the taskbar is currently visually stashed. 387 */ isStashed()388 public boolean isStashed() { 389 return mIsStashed; 390 } 391 392 /** 393 * Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash). 394 */ isStashedInApp()395 public boolean isStashedInApp() { 396 return hasAnyFlag(FLAGS_STASHED_IN_APP); 397 } 398 399 /** 400 * Returns whether the taskbar should be stashed in the current LauncherState. 401 */ isInStashedLauncherState()402 public boolean isInStashedLauncherState() { 403 return (hasAnyFlag(FLAG_IN_STASHED_LAUNCHER_STATE) && supportsVisualStashing()); 404 } 405 hasAnyFlag(long flagMask)406 private boolean hasAnyFlag(long flagMask) { 407 return hasAnyFlag(mState, flagMask); 408 } 409 hasAnyFlag(long flags, long flagMask)410 private boolean hasAnyFlag(long flags, long flagMask) { 411 return (flags & flagMask) != 0; 412 } 413 414 415 /** 416 * Returns whether the taskbar is currently visible and not in the process of being stashed. 417 */ isTaskbarVisibleAndNotStashing()418 public boolean isTaskbarVisibleAndNotStashing() { 419 return !mIsStashed && mControllers.taskbarViewController.areIconsVisible(); 420 } 421 isInApp()422 public boolean isInApp() { 423 return hasAnyFlag(FLAGS_IN_APP); 424 } 425 426 /** 427 * Returns the height that taskbar will be touchable. 428 */ getTouchableHeight()429 public int getTouchableHeight() { 430 return mIsStashed 431 ? mStashedHeight 432 : (mUnstashedHeight + mActivity.getDeviceProfile().taskbarBottomMargin); 433 } 434 435 /** 436 * Returns the height that taskbar will inset when inside apps. 437 * 438 * @see android.view.WindowInsets.Type#navigationBars() 439 * @see android.view.WindowInsets.Type#systemBars() 440 */ getContentHeightToReportToApps()441 public int getContentHeightToReportToApps() { 442 if (mActivity.isUserSetupComplete() && (mActivity.isPhoneGestureNavMode() 443 || DisplayController.isTransientTaskbar(mActivity))) { 444 return getStashedHeight(); 445 } 446 447 if (supportsVisualStashing() && hasAnyFlag(FLAGS_REPORT_STASHED_INSETS_TO_APP)) { 448 DeviceProfile dp = mActivity.getDeviceProfile(); 449 if (hasAnyFlag(FLAG_STASHED_IN_APP_SETUP) && (dp.isTaskbarPresent 450 || mActivity.isPhoneGestureNavMode())) { 451 // We always show the back button in SUW but in portrait the SUW layout may not 452 // be wide enough to support overlapping the nav bar with its content. 453 // We're sending different res values in portrait vs landscape 454 return mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_suw_insets); 455 } 456 boolean isAnimating = mAnimator != null && mAnimator.isStarted(); 457 if (!mControllers.stashedHandleViewController.isStashedHandleVisible() 458 && isInApp() 459 && !isAnimating) { 460 // We are in a settled state where we're not showing the handle even though taskbar 461 // is stashed. This can happen for example when home button is disabled (see 462 // StashedHandleViewController#setIsHomeButtonDisabled()). 463 return 0; 464 } 465 return mStashedHeight; 466 } 467 468 return mUnstashedHeight; 469 } 470 471 /** 472 * Returns the height that taskbar will inset when inside apps. 473 * 474 * @see android.view.WindowInsets.Type#tappableElement() 475 */ getTappableHeightToReportToApps()476 public int getTappableHeightToReportToApps() { 477 int contentHeight = getContentHeightToReportToApps(); 478 return contentHeight <= mStashedHeight ? 0 : contentHeight; 479 } 480 getStashedHeight()481 public int getStashedHeight() { 482 return mStashedHeight; 483 } 484 485 /** 486 * Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION. 487 * If bubble bar exists, it will match taskbars stashing behavior. 488 */ updateAndAnimateTransientTaskbar(boolean stash)489 public void updateAndAnimateTransientTaskbar(boolean stash) { 490 updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true); 491 } 492 493 /** 494 * Stash or unstashes the transient taskbar. 495 * 496 * @param stash whether transient taskbar should be stashed. 497 * @param shouldBubblesFollow whether bubbles should match taskbars behavior. 498 */ updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow)499 public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) { 500 if (!DisplayController.isTransientTaskbar(mActivity)) { 501 return; 502 } 503 504 if ( 505 stash 506 && !mControllers.taskbarAutohideSuspendController 507 .isSuspendedForTransientTaskbarInLauncher() 508 && mControllers.taskbarAutohideSuspendController 509 .isTransientTaskbarStashingSuspended()) { 510 // Avoid stashing if autohide is currently suspended. 511 return; 512 } 513 514 if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) { 515 mTaskbarSharedState.taskbarWasStashedAuto = stash; 516 updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash); 517 applyState(); 518 } 519 520 mControllers.bubbleControllers.ifPresent(controllers -> { 521 if (shouldBubblesFollow) { 522 final boolean willStash = mIsStashedPredicate.test(mState); 523 if (willStash != controllers.bubbleStashController.isStashed()) { 524 // Typically bubbles gets stashed / unstashed along with Taskbar, however, if 525 // taskbar is becoming stashed because bubbles is being expanded, we don't want 526 // to stash bubbles. 527 if (willStash) { 528 controllers.bubbleStashController.stashBubbleBar(); 529 } else { 530 controllers.bubbleStashController.showBubbleBar(false /* expandBubbles */); 531 } 532 } 533 } 534 }); 535 } 536 537 /** 538 * Stashes transient taskbar after it has timed out. 539 */ updateAndAnimateTransientTaskbarForTimeout()540 private void updateAndAnimateTransientTaskbarForTimeout() { 541 // If bubbles are expanded we shouldn't stash them when taskbar is hidden 542 // for the timeout. 543 boolean bubbleBarExpanded = mControllers.bubbleControllers.isPresent() 544 && mControllers.bubbleControllers.get().bubbleBarViewController.isExpanded(); 545 updateAndAnimateTransientTaskbar(/* stash= */ true, 546 /* shouldBubblesFollow= */ !bubbleBarExpanded); 547 } 548 549 /** Toggles the Taskbar's stash state. */ toggleTaskbarStash()550 public void toggleTaskbarStash() { 551 if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return; 552 updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)); 553 } 554 555 /** 556 * Adds the Taskbar unstash to Hotseat animator to the animator set. 557 * 558 * This should be used to run a Taskbar unstash to Hotseat animation whose progress matches a 559 * swipe progress. 560 * 561 * @param placeholderDuration a placeholder duration to be used to ensure all full-length 562 * sub-animations are properly coordinated. This duration should not 563 * actually be used since this animation tracks a swipe progress. 564 */ addUnstashToHotseatAnimationFromSuw(AnimatorSet animation, int placeholderDuration)565 protected void addUnstashToHotseatAnimationFromSuw(AnimatorSet animation, 566 int placeholderDuration) { 567 // Defer any UI updates now to avoid the UI becoming stale when the animation plays. 568 mControllers.taskbarViewController.setDeferUpdatesForSUW(true); 569 createAnimToIsStashed( 570 /* isStashed= */ mActivity.isPhoneMode(), 571 placeholderDuration, 572 TRANSITION_UNSTASH_SUW_MANUAL, 573 /* jankTag= */ "SUW_MANUAL"); 574 animation.addListener(AnimatorListeners.forEndCallback( 575 () -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false))); 576 animation.play(mAnimator); 577 } 578 579 /** 580 * Create a stash animation and save to {@link #mAnimator}. 581 * 582 * @param isStashed whether it's a stash animation or an unstash animation 583 * @param duration duration of the animation 584 * @param animationType what transition type to play. 585 * @param jankTag tag to be used in jank monitor trace. 586 */ createAnimToIsStashed(boolean isStashed, long duration, @StashAnimation int animationType, String jankTag)587 private void createAnimToIsStashed(boolean isStashed, long duration, 588 @StashAnimation int animationType, String jankTag) { 589 if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) { 590 // The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation. 591 Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar"); 592 } 593 594 if (mAnimator != null) { 595 mAnimator.cancel(); 596 } 597 mAnimator = new AnimatorSet(); 598 addJankMonitorListener( 599 mAnimator, /* expanding= */ !isStashed, /* tag= */ jankTag); 600 boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity); 601 final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar 602 ? 0 603 : (mUnstashedHeight - mStashedHeight); 604 605 if (!supportsVisualStashing()) { 606 // Just hide/show the icons and background instead of stashing into a handle. 607 mAnimator.play(mIconAlphaForStash.animateToValue(isStashed ? 0 : 1) 608 .setDuration(duration)); 609 mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0) 610 .setDuration(duration)); 611 mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed 612 ? stashTranslation : 0) 613 .setDuration(duration)); 614 mAnimator.play(mTaskbarImeBgAlpha.animateToValue( 615 (hasAnyFlag(FLAG_STASHED_IME) && isStashed) ? 0 : 1).setDuration( 616 duration)); 617 mAnimator.addListener(AnimatorListeners.forEndCallback(() -> { 618 mAnimator = null; 619 mIsStashed = isStashed; 620 onIsStashedChanged(mIsStashed); 621 })); 622 return; 623 } 624 625 if (isTransientTaskbar) { 626 createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType); 627 } else { 628 createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType); 629 } 630 631 mAnimator.addListener(new AnimatorListenerAdapter() { 632 @Override 633 public void onAnimationStart(Animator animation) { 634 mIsStashed = isStashed; 635 onIsStashedChanged(mIsStashed); 636 637 cancelTimeoutIfExists(); 638 } 639 640 @Override 641 public void onAnimationEnd(Animator animation) { 642 mAnimator = null; 643 644 if (!mIsStashed) { 645 tryStartTaskbarTimeout(); 646 } 647 648 // only announce if we are actually animating 649 if (duration > 0 && isInApp()) { 650 mControllers.taskbarViewController.announceForAccessibility(); 651 } 652 } 653 }); 654 } 655 createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, float stashTranslation, @StashAnimation int animationType)656 private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, 657 float stashTranslation, @StashAnimation int animationType) { 658 AnimatorSet fullLengthAnimatorSet = new AnimatorSet(); 659 // Not exactly half and may overlap. See [first|second]HalfDurationScale below. 660 AnimatorSet firstHalfAnimatorSet = new AnimatorSet(); 661 AnimatorSet secondHalfAnimatorSet = new AnimatorSet(); 662 663 final float firstHalfDurationScale; 664 final float secondHalfDurationScale; 665 666 if (isStashed) { 667 firstHalfDurationScale = 0.75f; 668 secondHalfDurationScale = 0.5f; 669 670 fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation)); 671 fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(1)); 672 673 firstHalfAnimatorSet.playTogether( 674 mIconAlphaForStash.animateToValue(0), 675 mIconScaleForStash.animateToValue(mActivity.isPhoneMode() ? 676 0 : STASHED_TASKBAR_SCALE) 677 ); 678 secondHalfAnimatorSet.playTogether( 679 mTaskbarStashedHandleAlpha.animateToValue(1) 680 ); 681 682 if (animationType == TRANSITION_HANDLE_FADE) { 683 fullLengthAnimatorSet.setInterpolator(INSTANT); 684 firstHalfAnimatorSet.setInterpolator(INSTANT); 685 } 686 } else { 687 firstHalfDurationScale = 0.5f; 688 secondHalfDurationScale = 0.75f; 689 690 fullLengthAnimatorSet.playTogether( 691 mIconScaleForStash.animateToValue(1), 692 mIconTranslationYForStash.animateToValue(0)); 693 694 final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL; 695 if (animateBg) { 696 fullLengthAnimatorSet.play(mTaskbarBackgroundOffset.animateToValue(0)); 697 } else { 698 fullLengthAnimatorSet.addListener(AnimatorListeners.forEndCallback( 699 () -> mTaskbarBackgroundOffset.updateValue(0))); 700 } 701 702 firstHalfAnimatorSet.playTogether( 703 mTaskbarStashedHandleAlpha.animateToValue(0) 704 ); 705 secondHalfAnimatorSet.playTogether( 706 mIconAlphaForStash.animateToValue(1) 707 ); 708 709 if (animationType == TRANSITION_HANDLE_FADE) { 710 fullLengthAnimatorSet.setInterpolator(FINAL_FRAME); 711 secondHalfAnimatorSet.setInterpolator(FINAL_FRAME); 712 } 713 } 714 715 fullLengthAnimatorSet.play(mControllers.stashedHandleViewController 716 .createRevealAnimToIsStashed(isStashed)); 717 // Return the stashed handle to its default scale in case it was changed as part of the 718 // feedforward hint. Note that the reveal animation above also visually scales it. 719 fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f)); 720 721 fullLengthAnimatorSet.setDuration(duration); 722 firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale)); 723 secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale)); 724 secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale))); 725 726 as.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, 727 secondHalfAnimatorSet); 728 729 } 730 createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, @StashAnimation int animationType)731 private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, 732 @StashAnimation int animationType) { 733 // Target values of the properties this is going to set 734 final float backgroundOffsetTarget = isStashed ? 1 : 0; 735 final float iconAlphaTarget = isStashed ? 0 : 1; 736 final float stashedHandleAlphaTarget = isStashed ? 1 : 0; 737 final float backgroundAlphaTarget = isStashed ? 0 : 1; 738 739 // Timing for the alpha values depend on the animation played 740 long iconAlphaStartDelay = 0, iconAlphaDuration = 0, backgroundAndHandleAlphaStartDelay = 0, 741 backgroundAndHandleAlphaDuration = 0; 742 if (duration > 0) { 743 if (animationType == TRANSITION_HANDLE_FADE) { 744 // When fading, the handle fades in/out at the beginning of the transition with 745 // TASKBAR_STASH_ALPHA_DURATION. 746 backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION; 747 // The iconAlphaDuration must be set to duration for the skippable interpolators 748 // below to work. 749 iconAlphaDuration = duration; 750 } else { 751 iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY; 752 iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION; 753 backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION; 754 755 if (isStashed) { 756 if (animationType == TRANSITION_HOME_TO_APP) { 757 iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY; 758 } 759 backgroundAndHandleAlphaStartDelay = iconAlphaStartDelay; 760 backgroundAndHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay); 761 } 762 763 } 764 } 765 766 play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget), 767 backgroundAndHandleAlphaStartDelay, 768 backgroundAndHandleAlphaDuration, LINEAR); 769 770 if (enableScalingRevealHomeAnimation() && !isStashed) { 771 play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration), 772 0, 0, LINEAR); 773 as.addListener(AnimatorListeners.forEndCallback( 774 () -> mTaskbarBackgroundAlphaForStash.setValue(backgroundAlphaTarget))); 775 } else { 776 play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget), 777 backgroundAndHandleAlphaStartDelay, 778 backgroundAndHandleAlphaDuration, LINEAR); 779 } 780 781 // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions. 782 AnimatorSet skippable = as; 783 if (animationType == TRANSITION_HANDLE_FADE) { 784 skippable = new AnimatorSet(); 785 as.play(skippable); 786 skippable.setInterpolator(isStashed ? INSTANT : FINAL_FRAME); 787 } 788 789 final boolean animateBg = animationType != TRANSITION_UNSTASH_SUW_MANUAL; 790 if (animateBg) { 791 play(skippable, mTaskbarBackgroundOffset.animateToValue(backgroundOffsetTarget), 0, 792 duration, EMPHASIZED); 793 } else { 794 skippable.addListener(AnimatorListeners.forEndCallback( 795 () -> mTaskbarBackgroundOffset.updateValue(backgroundOffsetTarget))); 796 } 797 798 play(skippable, mIconAlphaForStash.animateToValue(iconAlphaTarget), iconAlphaStartDelay, 799 iconAlphaDuration, 800 LINEAR); 801 802 if (isStashed) { 803 play(skippable, mControllers.taskbarSpringOnStashController.createSpringToStash(), 804 0, duration, LINEAR); 805 } else { 806 play(skippable, mControllers.taskbarSpringOnStashController.createResetAnimForUnstash(), 807 0, duration, LINEAR); 808 } 809 810 mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration, 811 EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL); 812 813 play(skippable, mControllers.stashedHandleViewController 814 .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED); 815 816 // Return the stashed handle to its default scale in case it was changed as part of the 817 // feedforward hint. Note that the reveal animation above also visually scales it. 818 skippable.play(mTaskbarStashedHandleHintScale.animateToValue(1f) 819 .setDuration(isStashed ? duration / 2 : duration)); 820 } 821 getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration)822 private Animator getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration) { 823 ValueAnimator a = ValueAnimator.ofFloat(0, 1); 824 a.setDuration(duration); 825 a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 826 // This value is arbitrary. 827 private static final float ANIMATED_FRACTION_THRESHOLD = 0.25f; 828 private boolean mTaskbarBgAlphaAnimationStarted = false; 829 @Override 830 public void onAnimationUpdate(ValueAnimator valueAnimator) { 831 if (mIsGoingHome) { 832 mTaskbarBgAlphaAnimationStarted = true; 833 } 834 if (mTaskbarBgAlphaAnimationStarted) { 835 return; 836 } 837 838 if (valueAnimator.getAnimatedFraction() >= ANIMATED_FRACTION_THRESHOLD) { 839 if (!mIsGoingHome) { 840 playTaskbarBackgroundAlphaAnimation(); 841 setUserIsGoingHome(false); 842 mTaskbarBgAlphaAnimationStarted = true; 843 } 844 } 845 } 846 }); 847 return a; 848 } 849 850 /** 851 * Sets whether the user is going home based on the current gesture. 852 */ setUserIsGoingHome(boolean isGoingHome)853 public void setUserIsGoingHome(boolean isGoingHome) { 854 mIsGoingHome = isGoingHome; 855 } 856 857 /** 858 * Plays the taskbar background alpha animation if one is not currently playing. 859 */ playTaskbarBackgroundAlphaAnimation()860 public void playTaskbarBackgroundAlphaAnimation() { 861 if (mTaskbarBackgroundAlphaAnimator != null 862 && mTaskbarBackgroundAlphaAnimator.isRunning()) { 863 return; 864 } 865 mTaskbarBackgroundAlphaAnimator = mTaskbarBackgroundAlphaForStash 866 .animateToValue(1f) 867 .setDuration(mTaskbarBackgroundDuration); 868 mTaskbarBackgroundAlphaAnimator.setInterpolator(LINEAR); 869 mTaskbarBackgroundAlphaAnimator.addListener(new AnimatorListenerAdapter() { 870 @Override 871 public void onAnimationEnd(Animator animation) { 872 mTaskbarBackgroundAlphaAnimator = null; 873 } 874 }); 875 mTaskbarBackgroundAlphaAnimator.start(); 876 } 877 play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration, Interpolator interpolator)878 private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration, 879 Interpolator interpolator) { 880 if (a == null) { 881 return; 882 } 883 a.setDuration(duration); 884 a.setStartDelay(startDelay); 885 a.setInterpolator(interpolator); 886 as.play(a); 887 } 888 addJankMonitorListener( AnimatorSet animator, boolean expanding, String tag)889 private void addJankMonitorListener( 890 AnimatorSet animator, boolean expanding, String tag) { 891 View v = mControllers.taskbarActivityContext.getDragLayer(); 892 if (!v.isAttachedToWindow()) { 893 // If the task bar drag layer is not attached to window, we don't need to monitor jank 894 // (actually we can't pass in an unattached view either). 895 return; 896 } 897 int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND : 898 InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE; 899 animator.addListener(new AnimatorListenerAdapter() { 900 @Override 901 public void onAnimationStart(@NonNull Animator animation) { 902 final Configuration.Builder builder = 903 Configuration.Builder.withView(action, v); 904 if (tag != null) { 905 builder.setTag(tag); 906 } 907 InteractionJankMonitor.getInstance().begin(builder); 908 } 909 910 @Override 911 public void onAnimationEnd(@NonNull Animator animation) { 912 InteractionJankMonitor.getInstance().end(action); 913 } 914 }); 915 } 916 917 /** 918 * Creates and starts a partial unstash animation, hinting at the new state that will trigger 919 * when long press is detected. 920 * 921 * @param animateForward Whether we are going towards the new unstashed state or returning to 922 * the stashed state. 923 */ startUnstashHint(boolean animateForward)924 protected void startUnstashHint(boolean animateForward) { 925 if (!isStashed()) { 926 // Already unstashed, no need to hint in that direction. 927 return; 928 } 929 mTaskbarStashedHandleHintScale.animateToValue( 930 animateForward ? UNSTASHED_TASKBAR_HANDLE_HINT_SCALE : 1) 931 .setDuration(TASKBAR_HINT_STASH_DURATION).start(); 932 } 933 onIsStashedChanged(boolean isStashed)934 private void onIsStashedChanged(boolean isStashed) { 935 mControllers.runAfterInit(() -> { 936 mControllers.stashedHandleViewController.onIsStashedChanged( 937 isStashed && supportsVisualStashing()); 938 mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged(); 939 }); 940 } 941 applyState()942 public void applyState() { 943 applyState(hasAnyFlag(FLAG_IN_SETUP) ? 0 : TASKBAR_STASH_DURATION); 944 } 945 applyState(long duration)946 public void applyState(long duration) { 947 Animator animator = createApplyStateAnimator(duration); 948 if (animator != null) { 949 animator.start(); 950 } 951 } 952 applyState(long duration, long startDelay)953 public void applyState(long duration, long startDelay) { 954 Animator animator = createApplyStateAnimator(duration); 955 if (animator != null) { 956 animator.setStartDelay(startDelay); 957 animator.start(); 958 } 959 } 960 961 /** 962 * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null} 963 * otherwise. 964 */ 965 @Nullable createApplyStateAnimator(long duration)966 public Animator createApplyStateAnimator(long duration) { 967 return mStatePropertyHolder.createSetStateAnimator(mState, duration); 968 } 969 970 /** 971 * Should be called when a system gesture starts and settles, so we can remove 972 * FLAG_STASHED_IN_APP_IME while the gesture is in progress. 973 */ setSystemGestureInProgress(boolean inProgress)974 public void setSystemGestureInProgress(boolean inProgress) { 975 mIsSystemGestureInProgress = inProgress; 976 setStashedImeState(); 977 } 978 setStashedImeState()979 private void setStashedImeState() { 980 boolean shouldStashForIme = shouldStashForIme(); 981 if (hasAnyFlag(FLAG_STASHED_IME) != shouldStashForIme) { 982 updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme); 983 applyState(TASKBAR_STASH_DURATION_FOR_IME, getTaskbarStashStartDelayForIme()); 984 } else { 985 applyState(mControllers.taskbarOverlayController.getCloseDuration()); 986 } 987 } 988 989 /** 990 * Should be called when Ime inset is changed to determine if taskbar should be stashed 991 */ onImeInsetChanged()992 public void onImeInsetChanged() { 993 setStashedImeState(); 994 } 995 996 /** 997 * When hiding the IME, delay the unstash animation to align with the end of the transition. 998 */ getTaskbarStashStartDelayForIme()999 private long getTaskbarStashStartDelayForIme() { 1000 if (mIsImeShowing) { 1001 // Only delay when IME is exiting, not entering. 1002 return 0; 1003 } 1004 // This duration is based on input_method_extract_exit.xml. 1005 long imeExitDuration = mControllers.taskbarActivityContext.getResources() 1006 .getInteger(android.R.integer.config_shortAnimTime); 1007 return imeExitDuration - TASKBAR_STASH_DURATION_FOR_IME; 1008 } 1009 1010 /** Called when some system ui state has changed. (See SYSUI_STATE_... in QuickstepContract) */ updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim)1011 public void updateStateForSysuiFlags(long systemUiStateFlags, boolean skipAnim) { 1012 long animDuration = TASKBAR_STASH_DURATION; 1013 long startDelay = 0; 1014 1015 updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags, 1016 SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE)); 1017 1018 boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW) 1019 && hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED) 1020 && DisplayController.isTransientTaskbar(mActivity); 1021 updateStateForFlag(FLAG_STASHED_SYSUI, 1022 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles); 1023 updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, 1024 SystemUiFlagUtils.isLocked(systemUiStateFlags)); 1025 1026 mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING); 1027 mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING); 1028 if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) { 1029 animDuration = TASKBAR_STASH_DURATION_FOR_IME; 1030 startDelay = getTaskbarStashStartDelayForIme(); 1031 } 1032 1033 applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay); 1034 } 1035 1036 /** 1037 * We stash when IME or IME switcher is showing. 1038 * 1039 * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape). 1040 * <p>Do not stash if taskbar is transient. 1041 * <p>Do not stash if hardware keyboard is attached and taskbar is pinned and IME is docked. 1042 * <p>Do not stash if a system gesture is started. 1043 */ shouldStashForIme()1044 private boolean shouldStashForIme() { 1045 if (DisplayController.isTransientTaskbar(mActivity)) { 1046 return false; 1047 } 1048 // Do not stash if in small screen, with 3 button nav, and in landscape. 1049 if (mActivity.isPhoneMode() && mActivity.isThreeButtonNav() 1050 && mActivity.getDeviceProfile().isLandscape) { 1051 return false; 1052 } 1053 1054 // Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked 1055 if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity) 1056 && !mActivity.isImeDocked()) { 1057 return false; 1058 } 1059 1060 // Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode 1061 DesktopVisibilityController visibilityController = 1062 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController(); 1063 if (visibilityController != null && mActivity.isHardwareKeyboard() 1064 && mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) { 1065 return false; 1066 } 1067 1068 // Do not stash if a gesture started. 1069 if (mIsSystemGestureInProgress) { 1070 return false; 1071 } 1072 1073 return mIsImeShowing || mIsImeSwitcherShowing; 1074 } 1075 1076 /** 1077 * Updates the proper flag to indicate whether the task bar should be stashed. 1078 * 1079 * Note that this only updates the flag. {@link #applyState()} needs to be called separately. 1080 * 1081 * @param flag The flag to update. 1082 * @param enabled Whether to enable the flag: True will cause the task bar to be stashed / 1083 * unstashed. 1084 * @return Whether the flag state changed. 1085 */ updateStateForFlag(long flag, boolean enabled)1086 public boolean updateStateForFlag(long flag, boolean enabled) { 1087 long oldState = mState; 1088 if (enabled) { 1089 mState |= flag; 1090 } else { 1091 mState &= ~flag; 1092 } 1093 return mState != oldState; 1094 } 1095 1096 /** 1097 * Called after updateStateForFlag() and applyState() have been called. 1098 * 1099 * @param changedFlags The flags that have changed. 1100 */ onStateChangeApplied(long changedFlags)1101 private void onStateChangeApplied(long changedFlags) { 1102 if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP)) { 1103 mControllers.uiController.onStashedInAppChanged(); 1104 } 1105 if (hasAnyFlag(changedFlags, FLAGS_STASHED_IN_APP | FLAGS_IN_APP)) { 1106 notifyStashChange(/* visible */ hasAnyFlag(FLAGS_IN_APP), 1107 /* stashed */ isStashedInApp()); 1108 mControllers.taskbarAutohideSuspendController.updateFlag( 1109 TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER, !isInApp()); 1110 } 1111 if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) { 1112 mActivity.getStatsLogManager().logger().log(hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) 1113 ? LAUNCHER_TRANSIENT_TASKBAR_HIDE 1114 : LAUNCHER_TRANSIENT_TASKBAR_SHOW); 1115 mControllers.taskbarAutohideSuspendController.updateFlag( 1116 TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR, 1117 !hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)); 1118 } 1119 mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp()); 1120 } 1121 notifyStashChange(boolean visible, boolean stashed)1122 private void notifyStashChange(boolean visible, boolean stashed) { 1123 mSystemUiProxy.notifyTaskbarStatus(visible, stashed); 1124 setUpTaskbarSystemAction(visible); 1125 mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed); 1126 } 1127 1128 /** 1129 * Setup system action for showing Taskbar depending on its visibility. 1130 */ setUpTaskbarSystemAction(boolean visible)1131 public void setUpTaskbarSystemAction(boolean visible) { 1132 UI_HELPER_EXECUTOR.execute(() -> { 1133 if (!visible || !DisplayController.isTransientTaskbar(mActivity)) { 1134 mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR); 1135 mIsTaskbarSystemActionRegistered = false; 1136 return; 1137 } 1138 1139 if (!mIsTaskbarSystemActionRegistered) { 1140 RemoteAction taskbarRemoteAction = new RemoteAction( 1141 Icon.createWithResource(mActivity, R.drawable.ic_info_no_shadow), 1142 mActivity.getString(R.string.taskbar_a11y_title), 1143 mActivity.getString(R.string.taskbar_a11y_title), 1144 mTaskbarSharedState.taskbarSystemActionPendingIntent); 1145 1146 mAccessibilityManager.registerSystemAction(taskbarRemoteAction, 1147 SYSTEM_ACTION_ID_TASKBAR); 1148 mIsTaskbarSystemActionRegistered = true; 1149 } 1150 }); 1151 } 1152 1153 /** 1154 * Clean up on destroy from TaskbarControllers 1155 */ onDestroy()1156 public void onDestroy() { 1157 UI_HELPER_EXECUTOR.execute( 1158 () -> mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR)); 1159 } 1160 1161 /** 1162 * Cancels a timeout if any exists. 1163 */ cancelTimeoutIfExists()1164 public void cancelTimeoutIfExists() { 1165 if (mTimeoutAlarm.alarmPending()) { 1166 mTimeoutAlarm.cancelAlarm(); 1167 } 1168 } 1169 1170 /** 1171 * Updates the status of the taskbar timeout. 1172 * 1173 * @param isAutohideSuspended If true, cancels any existing timeout 1174 * If false, attempts to re/start the timeout 1175 */ updateTaskbarTimeout(boolean isAutohideSuspended)1176 public void updateTaskbarTimeout(boolean isAutohideSuspended) { 1177 if (!DisplayController.isTransientTaskbar(mActivity)) { 1178 return; 1179 } 1180 if (isAutohideSuspended) { 1181 cancelTimeoutIfExists(); 1182 } else { 1183 tryStartTaskbarTimeout(); 1184 } 1185 } 1186 1187 /** 1188 * Attempts to start timer to auto hide the taskbar based on time. 1189 */ tryStartTaskbarTimeout()1190 public void tryStartTaskbarTimeout() { 1191 if (!DisplayController.isTransientTaskbar(mActivity) 1192 || mIsStashed 1193 || mEnableBlockingTimeoutDuringTests) { 1194 return; 1195 } 1196 1197 cancelTimeoutIfExists(); 1198 1199 mTimeoutAlarm.setOnAlarmListener(this::onTaskbarTimeout); 1200 mTimeoutAlarm.setAlarm(getTaskbarAutoHideTimeout()); 1201 } 1202 1203 /** 1204 * returns appropriate timeout for taskbar to stash depending on accessibility being on/off. 1205 */ getTaskbarAutoHideTimeout()1206 private long getTaskbarAutoHideTimeout() { 1207 return mAccessibilityManager.getRecommendedTimeoutMillis((int) NO_TOUCH_TIMEOUT_TO_STASH_MS, 1208 FLAG_CONTENT_CONTROLS); 1209 } 1210 onTaskbarTimeout(Alarm alarm)1211 private void onTaskbarTimeout(Alarm alarm) { 1212 if (mControllers.taskbarAutohideSuspendController.isTransientTaskbarStashingSuspended()) { 1213 return; 1214 } 1215 updateAndAnimateTransientTaskbarForTimeout(); 1216 } 1217 1218 @Override dumpLogs(String prefix, PrintWriter pw)1219 public void dumpLogs(String prefix, PrintWriter pw) { 1220 pw.println(prefix + "TaskbarStashController:"); 1221 1222 pw.println(prefix + "\tmStashedHeight=" + mStashedHeight); 1223 pw.println(prefix + "\tmUnstashedHeight=" + mUnstashedHeight); 1224 pw.println(prefix + "\tmIsStashed=" + mIsStashed); 1225 pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags)); 1226 pw.println(prefix + "\tmState=" + getStateString(mState)); 1227 pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress); 1228 pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing); 1229 pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing); 1230 } 1231 getStateString(long flags)1232 private static String getStateString(long flags) { 1233 StringJoiner sj = new StringJoiner("|"); 1234 appendFlag(sj, flags, FLAGS_IN_APP, "FLAG_IN_APP"); 1235 appendFlag(sj, flags, FLAG_STASHED_IN_APP_SYSUI, "FLAG_STASHED_IN_APP_SYSUI"); 1236 appendFlag(sj, flags, FLAG_STASHED_IN_APP_SETUP, "FLAG_STASHED_IN_APP_SETUP"); 1237 appendFlag(sj, flags, FLAG_STASHED_IME, "FLAG_STASHED_IN_APP_IME"); 1238 appendFlag(sj, flags, FLAG_IN_STASHED_LAUNCHER_STATE, "FLAG_IN_STASHED_LAUNCHER_STATE"); 1239 appendFlag(sj, flags, FLAG_STASHED_IN_TASKBAR_ALL_APPS, "FLAG_STASHED_IN_TASKBAR_ALL_APPS"); 1240 appendFlag(sj, flags, FLAG_IN_SETUP, "FLAG_IN_SETUP"); 1241 appendFlag(sj, flags, FLAG_STASHED_IN_APP_AUTO, "FLAG_STASHED_IN_APP_AUTO"); 1242 appendFlag(sj, flags, FLAG_STASHED_SYSUI, "FLAG_STASHED_SYSUI"); 1243 appendFlag(sj, flags, FLAG_STASHED_DEVICE_LOCKED, "FLAG_STASHED_DEVICE_LOCKED"); 1244 appendFlag(sj, flags, FLAG_IN_OVERVIEW, "FLAG_IN_OVERVIEW"); 1245 return sj.toString(); 1246 } 1247 1248 private class StatePropertyHolder { 1249 private final LongPredicate mStashCondition; 1250 1251 private boolean mIsStashed; 1252 private @StashAnimation int mLastStartedTransitionType = TRANSITION_DEFAULT; 1253 private long mPrevFlags; 1254 1255 private long mLastUnlockTransitionTimeout = 0; 1256 StatePropertyHolder(LongPredicate stashCondition)1257 StatePropertyHolder(LongPredicate stashCondition) { 1258 mStashCondition = stashCondition; 1259 } 1260 1261 /** 1262 * Creates an animator (stored in mAnimator) which applies the latest state, potentially 1263 * creating a new animation (stored in mAnimator). 1264 * 1265 * @param flags The latest flags to apply (see the top of this file). 1266 * @param duration The length of the animation. 1267 * @return mAnimator if mIsStashed changed, or {@code null} otherwise. 1268 */ 1269 @Nullable createSetStateAnimator(long flags, long duration)1270 public Animator createSetStateAnimator(long flags, long duration) { 1271 boolean isStashed = mStashCondition.test(flags); 1272 1273 if (DEBUG) { 1274 String stateString = formatFlagChange(flags, mPrevFlags, 1275 TaskbarStashController::getStateString); 1276 Log.d(TAG, "createSetStateAnimator: flags: " + stateString 1277 + ", duration: " + duration 1278 + ", isStashed: " + isStashed 1279 + ", mIsStashed: " + mIsStashed); 1280 } 1281 1282 long changedFlags = mPrevFlags ^ flags; 1283 if (mPrevFlags != flags) { 1284 onStateChangeApplied(changedFlags); 1285 mPrevFlags = flags; 1286 } 1287 1288 boolean isUnlockTransition = hasAnyFlag(changedFlags, FLAG_STASHED_DEVICE_LOCKED) 1289 && !hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED); 1290 if (isUnlockTransition) { 1291 // the launcher might not be resumed at the time the device is considered 1292 // unlocked (when the keyguard goes away), but possibly shortly afterwards. 1293 // To play the unlock transition at the time the unstash animation actually happens, 1294 // this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS. 1295 mLastUnlockTransitionTimeout = 1296 SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS; 1297 } 1298 1299 @StashAnimation int animationType = computeTransitionType(changedFlags); 1300 1301 // Allow re-starting animation if upgrading from default animation type, otherwise 1302 // stick with the already started transition. 1303 boolean transitionTypeChanged = mAnimator != null && mAnimator.isStarted() 1304 && mLastStartedTransitionType == TRANSITION_DEFAULT 1305 && animationType != TRANSITION_DEFAULT; 1306 1307 // It is possible for stash=false to be requested by TRANSITION_HOME_TO_APP and 1308 // TRANSITION_DEFAULT in quick succession. In this case, we should ignore 1309 // transitionTypeChanged because the animations are exactly the same. 1310 if (transitionTypeChanged 1311 && (!mIsStashed && !isStashed) 1312 && animationType == TRANSITION_HOME_TO_APP) { 1313 transitionTypeChanged = false; 1314 } 1315 1316 if (mIsStashed != isStashed || transitionTypeChanged) { 1317 mIsStashed = isStashed; 1318 mLastStartedTransitionType = animationType; 1319 1320 // This sets mAnimator. 1321 createAnimToIsStashed(mIsStashed, duration, animationType, 1322 computeTaskbarJankMonitorTag(changedFlags)); 1323 return mAnimator; 1324 } 1325 return null; 1326 } 1327 1328 /** Calculates the tag for CUJ_TASKBAR_EXPAND and CUJ_TASKBAR_COLLAPSE jank traces. */ computeTaskbarJankMonitorTag(long changedFlags)1329 private String computeTaskbarJankMonitorTag(long changedFlags) { 1330 if (hasAnyFlag(changedFlags, FLAG_IN_APP)) { 1331 // moving in or out of the app 1332 if (hasAnyFlag(FLAG_IN_APP)) { 1333 return "Home to App"; 1334 } else { 1335 return "App to Home"; 1336 } 1337 } 1338 if (hasAnyFlag(changedFlags, FLAG_STASHED_IN_APP_AUTO)) { 1339 // stash and unstash with-in the app 1340 if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO)) { 1341 return "Stashed in app"; 1342 } else { 1343 return "Manually unstashed"; 1344 } 1345 } 1346 return ""; 1347 } 1348 computeTransitionType(long changedFlags)1349 private @StashAnimation int computeTransitionType(long changedFlags) { 1350 1351 boolean hotseatHiddenDuringAppLaunch = 1352 !mControllers.uiController.isHotseatIconOnTopWhenAligned() 1353 && hasAnyFlag(changedFlags, FLAG_IN_APP); 1354 if (hotseatHiddenDuringAppLaunch) { 1355 // When launching an app from the all-apps drawer, the hotseat is hidden behind the 1356 // drawer. In this case, the navbar must just fade in, without a stash transition, 1357 // as the taskbar stash animation would otherwise be visible above the all-apps 1358 // drawer once the hotseat is detached. 1359 return TRANSITION_HANDLE_FADE; 1360 } 1361 1362 boolean isUnlockTransition = 1363 SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout; 1364 if (isUnlockTransition) { 1365 // When transitioning to unlocked device, the hotseat will already be visible on 1366 // the homescreen, thus do not play an un-stash animation. 1367 // Keep isUnlockTransition in sync with its counterpart in 1368 // TaskbarLauncherStateController#onStateChangeApplied. 1369 return TRANSITION_HANDLE_FADE; 1370 } 1371 1372 boolean homeToApp = hasAnyFlag(changedFlags, FLAG_IN_APP) && hasAnyFlag(FLAG_IN_APP); 1373 if (homeToApp) { 1374 return TRANSITION_HOME_TO_APP; 1375 } 1376 1377 return TRANSITION_DEFAULT; 1378 } 1379 } 1380 } 1381