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