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