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.animation.LayoutTransition.APPEARING; 19 import static android.animation.LayoutTransition.CHANGE_APPEARING; 20 import static android.animation.LayoutTransition.CHANGE_DISAPPEARING; 21 import static android.animation.LayoutTransition.DISAPPEARING; 22 import static android.view.Display.DEFAULT_DISPLAY; 23 import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION; 24 25 import static com.android.app.animation.Interpolators.EMPHASIZED; 26 import static com.android.app.animation.Interpolators.FINAL_FRAME; 27 import static com.android.app.animation.Interpolators.LINEAR; 28 import static com.android.launcher3.BubbleTextView.LINE_INDICATOR_ANIM_DURATION; 29 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 30 import static com.android.launcher3.Flags.taskbarOverflow; 31 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 32 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 33 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 34 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 35 import static com.android.launcher3.Utilities.mapRange; 36 import static com.android.launcher3.anim.AnimatedFloat.VALUE; 37 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 38 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; 39 import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning; 40 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT; 41 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT; 42 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS; 43 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS; 44 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 45 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_BAR_ANIM; 46 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_NAV_BAR_ANIM; 47 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM; 48 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM; 49 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM; 50 51 import android.animation.Animator; 52 import android.animation.AnimatorSet; 53 import android.animation.LayoutTransition; 54 import android.animation.LayoutTransition.TransitionListener; 55 import android.animation.ObjectAnimator; 56 import android.animation.ValueAnimator; 57 import android.annotation.NonNull; 58 import android.graphics.Rect; 59 import android.util.FloatProperty; 60 import android.util.Log; 61 import android.view.MotionEvent; 62 import android.view.View; 63 import android.view.ViewGroup; 64 import android.view.animation.Interpolator; 65 66 import androidx.annotation.Nullable; 67 import androidx.annotation.VisibleForTesting; 68 import androidx.core.view.OneShotPreDrawListener; 69 70 import com.android.app.animation.Interpolators; 71 import com.android.launcher3.BubbleTextView; 72 import com.android.launcher3.DeviceProfile; 73 import com.android.launcher3.LauncherAppState; 74 import com.android.launcher3.R; 75 import com.android.launcher3.Reorderable; 76 import com.android.launcher3.Utilities; 77 import com.android.launcher3.anim.AlphaUpdateListener; 78 import com.android.launcher3.anim.AnimatedFloat; 79 import com.android.launcher3.anim.AnimatorPlaybackController; 80 import com.android.launcher3.anim.PendingAnimation; 81 import com.android.launcher3.anim.RevealOutlineAnimation; 82 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 83 import com.android.launcher3.config.FeatureFlags; 84 import com.android.launcher3.model.data.ItemInfo; 85 import com.android.launcher3.model.data.TaskItemInfo; 86 import com.android.launcher3.taskbar.bubbles.BubbleBarController; 87 import com.android.launcher3.taskbar.bubbles.BubbleControllers; 88 import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer; 89 import com.android.launcher3.taskbar.customization.TaskbarDividerContainer; 90 import com.android.launcher3.util.ItemInfoMatcher; 91 import com.android.launcher3.util.LauncherBindableItemsContainer; 92 import com.android.launcher3.util.MultiPropertyFactory; 93 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty; 94 import com.android.launcher3.util.MultiTranslateDelegate; 95 import com.android.launcher3.util.MultiValueAlpha; 96 import com.android.launcher3.util.SandboxContext; 97 import com.android.quickstep.util.GroupTask; 98 import com.android.quickstep.util.SingleTask; 99 import com.android.systemui.shared.recents.model.Task; 100 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 101 102 import java.io.PrintWriter; 103 import java.util.Collections; 104 import java.util.HashSet; 105 import java.util.Set; 106 import java.util.function.Predicate; 107 108 /** 109 * Handles properties/data collection, then passes the results to TaskbarView to render. 110 */ 111 public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController, 112 BubbleBarController.BubbleBarLocationListener { 113 114 private static final String TAG = "TaskbarViewController"; 115 116 private static final Runnable NO_OP = () -> { }; 117 118 public static long TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS = 250; 119 120 public static final int ALPHA_INDEX_HOME = 0; 121 public static final int ALPHA_INDEX_KEYGUARD = 1; 122 public static final int ALPHA_INDEX_STASH = 2; 123 public static final int ALPHA_INDEX_RECENTS_DISABLED = 3; 124 public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4; 125 public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5; 126 public static final int ALPHA_INDEX_SMALL_SCREEN = 6; 127 public static final int ALPHA_INDEX_BUBBLE_BAR = 7; 128 public static final int ALPHA_INDEX_RECREATE = 8; 129 130 private static final int NUM_ALPHA_CHANNELS = 9; 131 132 /** Only used for animation purposes, to position the divider between two item indices. */ 133 public static final float DIVIDER_VIEW_POSITION_OFFSET = 0.5f; 134 135 /** Used if an unexpected edge case is hit in {@link #getPositionInHotseat}. */ 136 private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100; 137 138 private static final int TRANSITION_DELAY = 50; 139 private static final int TRANSITION_DEFAULT_DURATION = 500; 140 private static final int TRANSITION_FADE_IN_DURATION = 167; 141 private static final int TRANSITION_FADE_OUT_DURATION = 83; 142 private static final int APPEARING_LINE_INDICATOR_ANIM_DELAY = 143 TRANSITION_DEFAULT_DURATION - LINE_INDICATOR_ANIM_DURATION; 144 145 private final TaskbarActivityContext mActivity; 146 private @Nullable TaskbarDragLayerController mDragLayerController; 147 private final TaskbarView mTaskbarView; 148 private final MultiValueAlpha mTaskbarIconAlpha; 149 private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); 150 public final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( 151 this::updateTranslationY); 152 private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( 153 this::updateTranslationY); 154 155 private final AnimatedFloat mTaskbarIconScaleForPinning = new AnimatedFloat( 156 this::updateTaskbarIconsScale); 157 158 private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat( 159 () -> updateTaskbarIconTranslationXForPinning()); 160 161 private final AnimatedFloat mIconsTranslationXForNavbar = new AnimatedFloat( 162 this::updateTranslationXForNavBar); 163 164 private final AnimatedFloat mTranslationXForBubbleBar = new AnimatedFloat( 165 this::updateTranslationXForBubbleBar); 166 167 @Nullable 168 private Animator mTaskbarShiftXAnim; 169 @Nullable 170 private BubbleBarLocation mCurrentBubbleBarLocation; 171 @Nullable 172 private BubbleControllers mBubbleControllers = null; 173 @Nullable 174 private ObjectAnimator mTranslationXAnimation; 175 176 private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat( 177 this::updateTranslationY); 178 179 180 private AnimatedFloat mTaskbarNavButtonTranslationY; 181 private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay; 182 private float mTaskbarIconTranslationYForSwipe; 183 private float mTaskbarIconTranslationYForSpringOnStash; 184 185 private int mTaskbarBottomMargin; 186 private final int mStashedHandleHeight; 187 188 private final TaskbarModelCallbacks mModelCallbacks; 189 190 // Initialized in init. 191 private TaskbarControllers mControllers; 192 193 private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener = 194 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 195 if (!ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) { 196 // update shiftX is handled with the animation at the end of the method 197 updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ false); 198 } 199 if (mBubbleControllers == null) return; 200 mControllers.navbarButtonsViewController.onLayoutsUpdated(); 201 adjustTaskbarXForBubbleBar(); 202 }; 203 204 // Animation to align icons with Launcher, created lazily. This allows the controller to be 205 // active only during the animation and does not need to worry about layout changes. 206 private AnimatorPlaybackController mIconAlignControllerLazy = null; 207 private Runnable mOnControllerPreCreateCallback = NO_OP; 208 209 // Stored here as signals to determine if the mIconAlignController needs to be recreated. 210 private boolean mIsIconAlignedWithHotseat; 211 private boolean mIsHotseatIconOnTopWhenAligned; 212 private boolean mIsStashed; 213 214 private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener = 215 dp -> commitRunningAppsToUI(); 216 217 private final boolean mIsRtl; 218 219 private final DeviceProfile mTransientTaskbarDp; 220 private final DeviceProfile mPersistentTaskbarDp; 221 222 private final int mTransientIconSize; 223 private final int mPersistentIconSize; 224 225 private final float mTaskbarLeftRightMargin; 226 TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView)227 public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { 228 mActivity = activity; 229 mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile(); 230 mPersistentTaskbarDp = mActivity.getPersistentTaskbarDeviceProfile(); 231 mTransientIconSize = mTransientTaskbarDp.taskbarIconSize; 232 mPersistentIconSize = mPersistentTaskbarDp.taskbarIconSize; 233 mTaskbarView = taskbarView; 234 mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS); 235 mTaskbarIconAlpha.setUpdateVisibility(true); 236 mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity) 237 .create(mActivity, mTaskbarView); 238 mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin; 239 mStashedHandleHeight = activity.getResources() 240 .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height); 241 242 mIsRtl = Utilities.isRtl(mTaskbarView.getResources()); 243 mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize( 244 R.dimen.transient_taskbar_padding); 245 } 246 247 /** 248 * Init of taskbar view controller. 249 */ init(TaskbarControllers controllers, AnimatorSet startAnimation)250 public void init(TaskbarControllers controllers, AnimatorSet startAnimation) { 251 mControllers = controllers; 252 controllers.bubbleControllers.ifPresent(bc -> mBubbleControllers = bc); 253 254 if (startAnimation != null) { 255 MultiPropertyFactory<View>.MultiProperty multiProperty = 256 mTaskbarIconAlpha.get(ALPHA_INDEX_RECREATE); 257 multiProperty.setValue(0f); 258 Animator animator = multiProperty.animateToValue(1f); 259 animator.setInterpolator(EMPHASIZED); 260 startAnimation.play(animator); 261 } 262 263 mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create( 264 mActivity, mControllers, mTaskbarView)); 265 mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode() 266 ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size) 267 : mActivity.getDeviceProfile().taskbarHeight; 268 269 mTaskbarIconScaleForStash.updateValue(1f); 270 float pinningValue = 271 mActivity.isTransientTaskbar() ? PINNING_TRANSIENT : PINNING_PERSISTENT; 272 mTaskbarIconScaleForPinning.updateValue(pinningValue); 273 mTaskbarIconTranslationYForPinning.updateValue(pinningValue); 274 mTaskbarIconTranslationXForPinning.updateValue(pinningValue); 275 276 mModelCallbacks.init(controllers); 277 if (mActivity.isUserSetupComplete() 278 && !(mActivity.getApplicationContext() instanceof SandboxContext)) { 279 // Only load the callbacks if user setup is completed 280 controllers.runAfterInit(() -> LauncherAppState.getInstance(mActivity).getModel() 281 .addCallbacksAndLoad(mModelCallbacks)); 282 } 283 mTaskbarNavButtonTranslationY = 284 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY(); 285 mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController 286 .getTaskbarNavButtonTranslationYForInAppDisplay(); 287 mDragLayerController = controllers.taskbarDragLayerController; 288 mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 289 290 if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { 291 // This gets modified in NavbarButtonsViewController, but the initial value it reads 292 // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here 293 mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN).setValue( 294 mActivity.isPhoneMode() ? 0 : 1); 295 } 296 if (enableTaskbarPinning()) { 297 mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener); 298 } 299 } 300 301 /** Adjusts start aligned taskbar layout accordingly to the bubble bar position. */ 302 @Override onBubbleBarLocationUpdated(BubbleBarLocation location)303 public void onBubbleBarLocationUpdated(BubbleBarLocation location) { 304 updateCurrentBubbleBarLocation(location); 305 if (mActivity.isTransientTaskbar()) { 306 translateTaskbarXForBubbleBar(/* animate= */ false); 307 } else if (mActivity.shouldStartAlignTaskbar()) { 308 cancelTaskbarShiftAnimation(); 309 // reset translation x, taskbar will position icons with the updated location 310 mIconsTranslationXForNavbar.updateValue(0); 311 mTaskbarView.onBubbleBarLocationUpdated(location); 312 } 313 } 314 315 /** Animates start aligned taskbar accordingly to the bubble bar position. */ 316 @Override onBubbleBarLocationAnimated(BubbleBarLocation location)317 public void onBubbleBarLocationAnimated(BubbleBarLocation location) { 318 boolean locationUpdated = updateCurrentBubbleBarLocation(location); 319 if (mActivity.isTransientTaskbar()) { 320 translateTaskbarXForBubbleBar(/* animate= */ true); 321 } else if (locationUpdated && mActivity.shouldStartAlignTaskbar()) { 322 cancelTaskbarShiftAnimation(); 323 float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location); 324 mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX); 325 mTaskbarShiftXAnim.start(); 326 } 327 } 328 translateTaskbarXForBubbleBar(boolean animate)329 private void translateTaskbarXForBubbleBar(boolean animate) { 330 cancelCurrentTranslationXAnimation(); 331 if (!mActivity.isTransientTaskbar()) return; 332 int shiftX = getTransientTaskbarShiftXForBubbleBar(); 333 if (animate) { 334 mTranslationXAnimation = mTranslationXForBubbleBar.animateToValue(shiftX); 335 mTranslationXAnimation.setInterpolator(EMPHASIZED); 336 mTranslationXAnimation.setDuration(TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS); 337 mTranslationXAnimation.start(); 338 } else { 339 mTranslationXForBubbleBar.updateValue(shiftX); 340 } 341 } 342 cancelCurrentTranslationXAnimation()343 private void cancelCurrentTranslationXAnimation() { 344 if (mTranslationXAnimation != null) { 345 if (mTranslationXAnimation.isRunning()) { 346 mTranslationXAnimation.cancel(); 347 } 348 mTranslationXAnimation = null; 349 } 350 } 351 getTransientTaskbarShiftXForBubbleBar()352 private int getTransientTaskbarShiftXForBubbleBar() { 353 if (mBubbleControllers == null || !mActivity.isTransientTaskbar()) { 354 return 0; 355 } 356 return mBubbleControllers.bubbleBarViewController 357 .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation); 358 } 359 360 /** Updates the mCurrentBubbleBarLocation, returns {@code} true if location is updated. */ updateCurrentBubbleBarLocation(BubbleBarLocation location)361 private boolean updateCurrentBubbleBarLocation(BubbleBarLocation location) { 362 if (mCurrentBubbleBarLocation == location || location == null) { 363 return false; 364 } else { 365 mCurrentBubbleBarLocation = location; 366 return true; 367 } 368 } 369 cancelTaskbarShiftAnimation()370 private void cancelTaskbarShiftAnimation() { 371 if (mTaskbarShiftXAnim != null) { 372 mTaskbarShiftXAnim.cancel(); 373 } 374 } 375 376 /** 377 * Announcement for Accessibility when Taskbar stashes/unstashes. 378 */ announceForAccessibility()379 public void announceForAccessibility() { 380 mTaskbarView.announceAccessibilityChanges(); 381 } 382 383 /** 384 * Called with destroying Taskbar with animation. 385 */ onDestroyAnimation(AnimatorSet animatorSet)386 public void onDestroyAnimation(AnimatorSet animatorSet) { 387 animatorSet.play( 388 mTaskbarIconAlpha.get(TaskbarViewController.ALPHA_INDEX_RECREATE).animateToValue( 389 0f)); 390 } 391 onDestroy()392 public void onDestroy() { 393 if (enableTaskbarPinning()) { 394 mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener); 395 } 396 LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); 397 mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 398 } 399 400 /** 401 * Gets the taskbar {@link View.Visibility visibility}. 402 */ getTaskbarVisibility()403 public int getTaskbarVisibility() { 404 return mTaskbarView.getVisibility(); 405 } 406 areIconsVisible()407 public boolean areIconsVisible() { 408 return mTaskbarView.areIconsVisible(); 409 } 410 getTaskbarIconAlpha()411 public MultiPropertyFactory<View> getTaskbarIconAlpha() { 412 return mTaskbarIconAlpha; 413 } 414 415 /** 416 * Should be called when the recents button is disabled, so we can hide Taskbar icons as well. 417 */ setRecentsButtonDisabled(boolean isDisabled)418 public void setRecentsButtonDisabled(boolean isDisabled) { 419 // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha. 420 mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).animateToValue(isDisabled ? 0 : 1) 421 .start(); 422 } 423 424 /** 425 * Sets OnClickListener and OnLongClickListener for the given view. 426 */ setClickAndLongClickListenersForIcon(View icon)427 public void setClickAndLongClickListenersForIcon(View icon) { 428 mTaskbarView.setClickAndLongClickListenersForIcon(icon); 429 } 430 431 /** 432 * Adds one time pre draw listener to the Taskbar view, it is called before 433 * drawing a frame and invoked only once 434 * @param listener callback that will be invoked before drawing the next frame 435 */ addOneTimePreDrawListener(@onNull Runnable listener)436 public void addOneTimePreDrawListener(@NonNull Runnable listener) { 437 OneShotPreDrawListener.add(mTaskbarView, listener); 438 } 439 440 @VisibleForTesting getMaxNumIconViews()441 int getMaxNumIconViews() { 442 return mTaskbarView.getMaxNumIconViews(); 443 } 444 getTransientTaskbarIconLayoutBounds()445 public Rect getTransientTaskbarIconLayoutBounds() { 446 return mTaskbarView.getTransientTaskbarIconLayoutBounds(); 447 } 448 getTransientTaskbarIconLayoutBoundsInParent()449 public Rect getTransientTaskbarIconLayoutBoundsInParent() { 450 return mTaskbarView.getTransientTaskbarIconLayoutBoundsInParent(); 451 } 452 getIconViews()453 public View[] getIconViews() { 454 return mTaskbarView.getIconViews(); 455 } 456 getAllAppsButtonView()457 public View getAllAppsButtonView() { 458 return mTaskbarView.getAllAppsButtonContainer(); 459 } 460 getTaskbarIconScaleForStash()461 public AnimatedFloat getTaskbarIconScaleForStash() { 462 return mTaskbarIconScaleForStash; 463 } 464 getTaskbarIconTranslationYForStash()465 public AnimatedFloat getTaskbarIconTranslationYForStash() { 466 return mTaskbarIconTranslationYForStash; 467 } 468 getTaskbarIconScaleForPinning()469 public AnimatedFloat getTaskbarIconScaleForPinning() { 470 return mTaskbarIconScaleForPinning; 471 } 472 getTaskbarIconTranslationXForPinning()473 public AnimatedFloat getTaskbarIconTranslationXForPinning() { 474 return mTaskbarIconTranslationXForPinning; 475 } 476 getTaskbarIconTranslationYForPinning()477 public AnimatedFloat getTaskbarIconTranslationYForPinning() { 478 return mTaskbarIconTranslationYForPinning; 479 } 480 481 /** 482 * Applies scale properties for the entire TaskbarView (rather than individual icons). 483 */ updateScale()484 private void updateScale() { 485 float scale = mTaskbarIconScaleForStash.value; 486 mTaskbarView.setScaleX(scale); 487 mTaskbarView.setScaleY(scale); 488 } 489 490 /** 491 * Applies scale properties for the taskbar icons 492 */ updateTaskbarIconsScale()493 private void updateTaskbarIconsScale() { 494 float scale = mTaskbarIconScaleForPinning.value; 495 View[] iconViews = mTaskbarView.getIconViews(); 496 497 float finalScale; 498 TaskbarSharedState sharedState = mControllers.getSharedState(); 499 if (sharedState != null && sharedState.startTaskbarVariantIsTransient) { 500 finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize)); 501 } else { 502 finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f); 503 } 504 505 for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) { 506 iconViews[iconIndex].setScaleX(finalScale); 507 iconViews[iconIndex].setScaleY(finalScale); 508 } 509 } 510 511 /** 512 * Animate away taskbar icon notification dots during the taskbar pinning animation. 513 */ animateAwayNotificationDotsDuringTaskbarPinningAnimation()514 public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() { 515 for (View iconView : mTaskbarView.getIconViews()) { 516 if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) { 517 ((BubbleTextView) iconView).animateDotScale(0); 518 } 519 } 520 } 521 updateTaskbarIconTranslationXForPinning()522 void updateTaskbarIconTranslationXForPinning() { 523 updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ true); 524 } 525 updateTaskbarIconTranslationXForPinning(boolean updateShiftXForBubbleBar)526 void updateTaskbarIconTranslationXForPinning(boolean updateShiftXForBubbleBar) { 527 View[] iconViews = mTaskbarView.getIconViews(); 528 float scale = mTaskbarIconTranslationXForPinning.value; 529 float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension( 530 mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true)); 531 float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension( 532 mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false)); 533 if (mBubbleControllers != null && updateShiftXForBubbleBar) { 534 cancelCurrentTranslationXAnimation(); 535 int translationXForTransientTaskbar = mBubbleControllers.bubbleBarViewController 536 .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation); 537 float currentTranslationXForTransientTaskbar = mapRange(scale, 538 translationXForTransientTaskbar, 0); 539 mTranslationXForBubbleBar.updateValue(currentTranslationXForTransientTaskbar); 540 } 541 float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset, 542 persistentTaskbarAllAppsOffset); 543 // Task icons are laid out so the taskbar content is centered. The taskbar width (used for 544 // centering taskbar icons) depends on the all apps button X translation, and is different 545 // for persistent and transient taskbar. If the offset used for current taskbar layout is 546 // different than the offset used in final taskbar state, the icons may jump when the 547 // animation completes, and the taskbar is replaced. Adjust item transform to account for 548 // this mismatch. 549 float sizeDiffTranslationRange = 550 mapRange(scale, 551 (mTaskbarView.getAllAppsButtonTranslationXOffsetUsedForLayout() 552 - transientTaskbarAllAppsOffset) / 2, 553 (mTaskbarView.getAllAppsButtonTranslationXOffsetUsedForLayout() 554 - persistentTaskbarAllAppsOffset) / 2); 555 556 // no x translation required when all apps button is the only icon in taskbar. 557 if (iconViews.length <= 1) { 558 allAppIconTranslateRange = 0f; 559 } 560 561 if (mIsRtl) { 562 allAppIconTranslateRange *= -1; 563 sizeDiffTranslationRange *= -1; 564 } 565 566 if (mActivity.isThreeButtonNav()) { 567 mTaskbarView.getAllAppsButtonContainer() 568 .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange); 569 return; 570 } 571 572 float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize); 573 574 // The index of the "middle" icon which will be used as a index from which the icon margins 575 // will be scaled. If number of icons is even, using the middle point between indices of two 576 // central icons. 577 float middleIndex = (iconViews.length - 1) / 2.0f; 578 for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) { 579 View iconView = iconViews[iconIndex]; 580 MultiTranslateDelegate translateDelegate = 581 ((Reorderable) iconView).getTranslateDelegate(); 582 translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue( 583 finalMarginScale * (middleIndex - iconIndex) + sizeDiffTranslationRange); 584 585 if (iconView.equals(mTaskbarView.getAllAppsButtonContainer())) { 586 mTaskbarView.getAllAppsButtonContainer().setTranslationXForTaskbarAllAppsIcon( 587 allAppIconTranslateRange); 588 } 589 } 590 } 591 592 /** 593 * Calculates visual taskbar view width. 594 */ getCurrentVisualTaskbarWidth()595 public float getCurrentVisualTaskbarWidth() { 596 View[] iconViews = mTaskbarView.getIconViews(); 597 if (iconViews.length == 0) { 598 return 0; 599 } 600 601 float left = iconViews[0].getX(); 602 603 int rightIndex = iconViews.length - 1; 604 float right = iconViews[rightIndex].getRight() + iconViews[rightIndex].getTranslationX(); 605 606 return right - left + (2 * mTaskbarLeftRightMargin); 607 } 608 609 /** 610 * Sets the translation of the TaskbarView during the swipe up gesture. 611 */ setTranslationYForSwipe(float transY)612 public void setTranslationYForSwipe(float transY) { 613 mTaskbarIconTranslationYForSwipe = transY; 614 updateTranslationY(); 615 } 616 617 /** 618 * Sets the translation of the TaskbarView during the spring on stash animation. 619 */ setTranslationYForStash(float transY)620 public void setTranslationYForStash(float transY) { 621 mTaskbarIconTranslationYForSpringOnStash = transY; 622 updateTranslationY(); 623 } 624 updateTranslationY()625 private void updateTranslationY() { 626 mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value 627 + mTaskbarIconTranslationYForStash.value 628 + mTaskbarIconTranslationYForSwipe 629 + getTaskbarIconTranslationYForPinningValue() 630 + mTaskbarIconTranslationYForSpringOnStash); 631 } 632 updateTranslationXForNavBar()633 private void updateTranslationXForNavBar() { 634 updateIconViewsTranslationX(INDEX_NAV_BAR_ANIM, mIconsTranslationXForNavbar.value); 635 } 636 updateTranslationXForBubbleBar()637 private void updateTranslationXForBubbleBar() { 638 float translationX = mTranslationXForBubbleBar.value; 639 updateIconViewsTranslationX(INDEX_BUBBLE_BAR_ANIM, translationX); 640 if (mDragLayerController != null) { 641 mDragLayerController.setTranslationXForBubbleBar(translationX); 642 } 643 } 644 updateIconViewsTranslationX(int translationXChannel, float translationX)645 private void updateIconViewsTranslationX(int translationXChannel, float translationX) { 646 View[] iconViews = mTaskbarView.getIconViews(); 647 for (View iconView : iconViews) { 648 MultiTranslateDelegate translateDelegate = 649 ((Reorderable) iconView).getTranslateDelegate(); 650 translateDelegate.getTranslationX(translationXChannel).setValue(translationX); 651 } 652 } 653 654 /** 655 * Computes translation y for taskbar pinning. 656 */ getTaskbarIconTranslationYForPinningValue()657 private float getTaskbarIconTranslationYForPinningValue() { 658 if (mControllers.getSharedState() == null) return 0f; 659 660 float scale = mTaskbarIconTranslationYForPinning.value; 661 float taskbarIconTranslationYForPinningValue; 662 663 // transY is calculated here by adding/subtracting the taskbar bottom margin 664 // aligning the icon bound to be at bottom of current taskbar view and then 665 // finally placing the icon in the middle of new taskbar background height. 666 if (mControllers.getSharedState().startTaskbarVariantIsTransient) { 667 float transY = 668 mTransientTaskbarDp.taskbarBottomMargin + (mTransientTaskbarDp.taskbarHeight 669 - mTaskbarView.getTransientTaskbarIconLayoutBounds().bottom) 670 - (mPersistentTaskbarDp.taskbarHeight 671 - mTransientTaskbarDp.taskbarIconSize) / 2f; 672 taskbarIconTranslationYForPinningValue = mapRange(scale, 0f, transY); 673 } else { 674 float transY = 675 -mTransientTaskbarDp.taskbarBottomMargin + (mPersistentTaskbarDp.taskbarHeight 676 - mTaskbarView.getTransientTaskbarIconLayoutBounds().bottom) 677 - (mTransientTaskbarDp.taskbarHeight 678 - mTransientTaskbarDp.taskbarIconSize) / 2f; 679 taskbarIconTranslationYForPinningValue = mapRange(scale, transY, 0f); 680 } 681 return taskbarIconTranslationYForPinningValue; 682 } 683 createRevealAnimForView(View view, boolean isStashed, float newWidth, boolean isQsb, boolean dispatchOnAnimationStart)684 private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth, 685 boolean isQsb, boolean dispatchOnAnimationStart) { 686 Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight()); 687 int centerY = viewBounds.centerY(); 688 int halfHandleHeight = mStashedHandleHeight / 2; 689 final int top = centerY - halfHandleHeight; 690 final int bottom = centerY + halfHandleHeight; 691 692 final int left; 693 final int right; 694 // QSB will crop from the 'start' whereas all other icons will crop from the center. 695 if (isQsb) { 696 if (mIsRtl) { 697 right = viewBounds.right; 698 left = (int) (right - newWidth); 699 } else { 700 left = viewBounds.left; 701 right = (int) (left + newWidth); 702 } 703 } else { 704 int widthDelta = (int) ((viewBounds.width() - newWidth) / 2); 705 706 left = viewBounds.left + widthDelta; 707 right = viewBounds.right - widthDelta; 708 } 709 710 Rect stashedRect = new Rect(left, top, right, bottom); 711 // QSB radius can be > 0 since it does not have any UI elements outside of it bounds. 712 float radius = isQsb 713 ? viewBounds.height() / 2f 714 : 0f; 715 float stashedRadius = stashedRect.height() / 2f; 716 717 ValueAnimator reveal = new RoundedRectRevealOutlineProvider(radius, 718 stashedRadius, viewBounds, stashedRect) 719 .createRevealAnimator(view, !isStashed, 0); 720 // SUW animation does not dispatch animation start until *after* the animation is complete. 721 // In order to work properly, the reveal animation start needs to be called immediately. 722 if (dispatchOnAnimationStart) { 723 for (Animator.AnimatorListener listener : reveal.getListeners()) { 724 listener.onAnimationStart(reveal); 725 } 726 } 727 return reveal; 728 } 729 getTaskbarDividerView()730 public View getTaskbarDividerView() { 731 return mTaskbarView.getTaskbarDividerViewContainer(); 732 } 733 734 /** 735 * Updates which icons are marked as running or minimized given the Sets of currently running 736 * and minimized tasks. 737 */ updateIconViewsRunningStates()738 public void updateIconViewsRunningStates() { 739 for (View iconView : getIconViews()) { 740 if (iconView instanceof BubbleTextView btv) { 741 updateRunningState(btv); 742 if (shouldUpdateIconContentDescription(btv)) { 743 btv.setContentDescription( 744 btv.getContentDescription() + " " + btv.getIconStateDescription()); 745 } 746 } 747 } 748 } 749 shouldUpdateIconContentDescription(BubbleTextView btv)750 private boolean shouldUpdateIconContentDescription(BubbleTextView btv) { 751 boolean isInDesktopMode = mControllers.taskbarDesktopModeController.isInDesktopMode( 752 DEFAULT_DISPLAY); 753 boolean isAllAppsButton = btv instanceof TaskbarAllAppsButtonContainer; 754 boolean isDividerButton = btv instanceof TaskbarDividerContainer; 755 return isInDesktopMode && !isAllAppsButton && !isDividerButton; 756 } 757 758 /** 759 * @return A set of Task ids of running apps that are pinned in the taskbar. 760 */ getTaskIdsForPinnedApps()761 protected Set<Integer> getTaskIdsForPinnedApps() { 762 if (!taskbarOverflow()) { 763 return Collections.emptySet(); 764 } 765 766 Set<Integer> pinnedAppsWithTasks = new HashSet<>(); 767 for (View iconView : getIconViews()) { 768 if (iconView instanceof BubbleTextView btv 769 && btv.getTag() instanceof TaskItemInfo itemInfo) { 770 pinnedAppsWithTasks.add(itemInfo.getTaskId()); 771 } 772 } 773 return pinnedAppsWithTasks; 774 } 775 updateRunningState(BubbleTextView btv)776 private void updateRunningState(BubbleTextView btv) { 777 btv.updateRunningState(getRunningAppState(btv), mTaskbarView.getLayoutTransition() != null); 778 } 779 getRunningAppState(BubbleTextView btv)780 private BubbleTextView.RunningAppState getRunningAppState(BubbleTextView btv) { 781 Object tag = btv.getTag(); 782 if (tag instanceof TaskItemInfo itemInfo) { 783 return mControllers.taskbarRecentAppsController.getRunningAppState( 784 itemInfo.getTaskId()); 785 } 786 if (tag instanceof SingleTask singleTask) { 787 return mControllers.taskbarRecentAppsController.getRunningAppState( 788 singleTask.getTask().key.id); 789 } 790 return BubbleTextView.RunningAppState.NOT_RUNNING; 791 } 792 793 /** 794 * Defers any updates to the UI for the setup wizard animation. 795 */ setDeferUpdatesForSUW(boolean defer)796 public void setDeferUpdatesForSUW(boolean defer) { 797 mModelCallbacks.setDeferUpdatesForSUW(defer); 798 } 799 800 /** 801 * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape 802 * and size. 803 * @param as The AnimatorSet to add all animations to. 804 * @param isStashed When true, the icon crops vertically to the size of the stashed handle. 805 * When false, the reverse happens. 806 * @param duration The duration of the animation. 807 * @param interpolator The interpolator to use for all animations. 808 */ addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, Interpolator interpolator, boolean dispatchOnAnimationStart)809 public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, 810 Interpolator interpolator, boolean dispatchOnAnimationStart) { 811 AnimatorSet reveal = new AnimatorSet(); 812 813 Rect stashedBounds = new Rect(); 814 mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds); 815 816 int numIcons = mTaskbarView.getChildCount(); 817 float newChildWidth = stashedBounds.width() / (float) numIcons; 818 819 // All children move the same y-amount since they will be cropped to the same centerY. 820 float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height(); 821 822 for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) { 823 View child = mTaskbarView.getChildAt(i); 824 boolean isQsb = child == mTaskbarView.getQsb(); 825 826 // Crop the icons to/from the nav handle shape. 827 reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb, 828 dispatchOnAnimationStart).setDuration(duration)); 829 830 // Translate the icons to/from their locations as the "nav handle." 831 832 // All of the Taskbar icons will overlap the entirety of the stashed handle 833 // And the QSB, if inline, will overlap part of stashed handle as well. 834 float currentPosition = isQsb ? child.getX() : child.getLeft(); 835 float newPosition = stashedBounds.left + (newChildWidth * i); 836 final float croppedTransX; 837 // We look at 'left' and 'right' values to ensure that the children stay within the 838 // bounds of the stashed handle since the new width only occurs at the end of the anim. 839 if (currentPosition > newPosition) { 840 float newRight = stashedBounds.right - (newChildWidth 841 * (numIcons - 1 - i)); 842 croppedTransX = -(currentPosition + child.getWidth() - newRight); 843 } else { 844 croppedTransX = newPosition - currentPosition; 845 } 846 float[] transX = isStashed 847 ? new float[] {croppedTransX} 848 : new float[] {croppedTransX, 0}; 849 float[] transY = isStashed 850 ? new float[] {croppedTransY} 851 : new float[] {croppedTransY, 0}; 852 853 if (child instanceof Reorderable) { 854 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 855 856 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM), 857 MULTI_PROPERTY_VALUE, transX) 858 .setDuration(duration)); 859 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), 860 MULTI_PROPERTY_VALUE, transY)); 861 as.addListener(forEndCallback(() -> 862 mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0))); 863 } else { 864 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX) 865 .setDuration(duration)); 866 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY)); 867 as.addListener(forEndCallback(() -> { 868 child.setTranslationX(0); 869 child.setTranslationY(0); 870 })); 871 } 872 } 873 874 reveal.setInterpolator(interpolator); 875 as.play(reveal); 876 } 877 878 /** 879 * Sets the Taskbar icon alignment relative to Launcher hotseat icons 880 * @param alignmentRatio [0, 1] 881 * 0 => not aligned 882 * 1 => fully aligned 883 */ setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp)884 public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) { 885 if (mActivity.isPhoneMode()) { 886 mIconAlignControllerLazy = null; 887 return; 888 } 889 boolean isHotseatIconOnTopWhenAligned = 890 mControllers.uiController.isHotseatIconOnTopWhenAligned(); 891 boolean isIconAlignedWithHotseat = mControllers.uiController.isIconAlignedWithHotseat(); 892 boolean isStashed = mControllers.taskbarStashController.isStashed(); 893 // Re-create animation when any of these values change. 894 if (mIconAlignControllerLazy == null 895 || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned 896 || mIsIconAlignedWithHotseat != isIconAlignedWithHotseat 897 || mIsStashed != isStashed) { 898 mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned; 899 mIsIconAlignedWithHotseat = isIconAlignedWithHotseat; 900 mIsStashed = isStashed; 901 mIconAlignControllerLazy = createIconAlignmentController(launcherDp); 902 } 903 mIconAlignControllerLazy.setPlayFraction(alignmentRatio); 904 if (alignmentRatio <= 0 || alignmentRatio >= 1) { 905 // Cleanup lazy controller so that it is created again in next animation 906 mIconAlignControllerLazy = null; 907 } 908 } 909 910 /** 911 * Resets the icon alignment controller so that it can be recreated again later, and updates 912 * the list of icons shown in the taskbar if the bubble bar visibility changes the taskbar 913 * overflow state. 914 */ adjustTaskbarForBubbleBar()915 void adjustTaskbarForBubbleBar() { 916 mIconAlignControllerLazy = null; 917 if (mTaskbarView.updateMaxNumIcons()) { 918 commitRunningAppsToUI(); 919 } 920 adjustTaskbarXForBubbleBar(); 921 } 922 adjustTaskbarXForBubbleBar()923 private void adjustTaskbarXForBubbleBar() { 924 if (mBubbleControllers != null && mActivity.isTransientTaskbar()) { 925 translateTaskbarXForBubbleBar(/* animate= */ true); 926 } 927 } 928 929 /** 930 * Creates an animation for aligning the Taskbar icons with the provided Launcher device profile 931 */ createIconAlignmentController(DeviceProfile launcherDp)932 private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) { 933 PendingAnimation setter = new PendingAnimation(100); 934 // icon alignment not needed for pinned taskbar. 935 if (mActivity.isPinnedTaskbar()) { 936 return setter.createPlaybackController(); 937 } 938 mOnControllerPreCreateCallback.run(); 939 DeviceProfile taskbarDp = mActivity.getDeviceProfile(); 940 Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity); 941 boolean isTransientTaskbar = mActivity.isTransientTaskbar(); 942 943 float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize; 944 int borderSpacing = launcherDp.hotseatBorderSpace; 945 int hotseatCellSize = DeviceProfile.calculateCellWidth( 946 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right, 947 borderSpacing, 948 launcherDp.numShownHotseatIcons); 949 950 boolean isToHome = mControllers.uiController.isIconAlignedWithHotseat(); 951 boolean isDeviceLocked = mControllers.taskbarStashController.isDeviceLocked(); 952 // If Hotseat is not the top element, Taskbar should maintain in-app state as it fades out, 953 // or fade in while already in in-app state. 954 Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME; 955 956 int offsetY = 957 isDeviceLocked ? taskbarDp.getTaskbarOffsetY() : launcherDp.getTaskbarOffsetY(); 958 setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator); 959 setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator); 960 setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator); 961 if (mBubbleControllers != null 962 && mCurrentBubbleBarLocation != null 963 && mActivity.isTransientTaskbar()) { 964 int offsetX = mBubbleControllers.bubbleBarViewController 965 .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation); 966 if (offsetX != 0) { 967 // if taskbar should be adjusted for the bubble bar adjust the taskbar translation 968 mTranslationXForBubbleBar.updateValue(offsetX); 969 setter.setFloat(mTranslationXForBubbleBar, VALUE, 0, interpolator); 970 } 971 } 972 int collapsedHeight = mActivity.getDefaultTaskbarWindowSize(); 973 int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY); 974 setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowSize( 975 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight)); 976 977 mTaskbarBottomMargin = isTransientTaskbar 978 ? mTransientTaskbarDp.taskbarBottomMargin 979 : mPersistentTaskbarDp.taskbarBottomMargin; 980 981 int firstRecentTaskIndex = -1; 982 int hotseatNavBarTranslationX = 0; 983 if (mCurrentBubbleBarLocation != null) { 984 boolean isBubblesOnLeft = mCurrentBubbleBarLocation 985 .isOnLeft(mTaskbarView.isLayoutRtl()); 986 hotseatNavBarTranslationX = taskbarDp 987 .getHotseatTranslationXForNavBar(mActivity, isBubblesOnLeft); 988 } 989 for (int i = 0; i < mTaskbarView.getChildCount(); i++) { 990 View child = mTaskbarView.getChildAt(i); 991 boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer(); 992 boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerViewContainer(); 993 boolean isTaskbarOverflowView = child == mTaskbarView.getTaskbarOverflowView(); 994 boolean isRecentTask = child.getTag() instanceof GroupTask; 995 // TODO(b/343522351): show recents on the home screen. 996 final boolean isRecentsInHotseat = false; 997 if (!mIsHotseatIconOnTopWhenAligned) { 998 // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController 999 // plays iconAlignment to 1 really fast, therefore moving the fading towards the end 1000 // to avoid icons disappearing rather than fading out visually. 1001 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f)); 1002 } else if ((isAllAppsButton && !FeatureFlags.enableAllAppsButtonInHotseat()) 1003 || (isTaskbarDividerView && enableTaskbarPinning()) 1004 || (isRecentTask && !isRecentsInHotseat) 1005 || isTaskbarOverflowView) { 1006 if (!isToHome 1007 && mIsHotseatIconOnTopWhenAligned 1008 && mIsStashed) { 1009 // Prevent All Apps icon from appearing when going from hotseat to nav handle. 1010 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f)); 1011 } else if (enableScalingRevealHomeAnimation()) { 1012 // Tighten clamp so that these icons do not linger as the spring settles. 1013 setter.setViewAlpha(child, 0, 1014 isToHome 1015 ? Interpolators.clampToProgress(LINEAR, 0f, 0.07f) 1016 : Interpolators.clampToProgress(LINEAR, 0.93f, 1f)); 1017 } else { 1018 setter.setViewAlpha(child, 0, 1019 isToHome 1020 ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f) 1021 : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f)); 1022 } 1023 } 1024 if (child == mTaskbarView.getQsb()) { 1025 boolean isRtl = Utilities.isRtl(child.getResources()); 1026 float hotseatIconCenter = isRtl 1027 ? launcherDp.widthPx - hotseatPadding.right + borderSpacing 1028 + launcherDp.hotseatQsbWidth / 2f 1029 : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f; 1030 if (taskbarDp.isQsbInline) { 1031 hotseatIconCenter += hotseatNavBarTranslationX; 1032 } 1033 float childCenter = (child.getLeft() + child.getRight()) / 2f; 1034 if (child instanceof Reorderable reorderableChild) { 1035 childCenter += reorderableChild.getTranslateDelegate().getTranslationX( 1036 INDEX_TASKBAR_PINNING_ANIM).getValue(); 1037 } 1038 float halfQsbIconWidthDiff = 1039 (launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f; 1040 float scale = ((float) taskbarDp.taskbarIconSize) 1041 / launcherDp.hotseatQsbVisualHeight; 1042 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator); 1043 1044 float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff; 1045 float toX = hotseatIconCenter - childCenter; 1046 if (child instanceof Reorderable reorderableChild) { 1047 MultiTranslateDelegate mtd = reorderableChild.getTranslateDelegate(); 1048 1049 setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 1050 MULTI_PROPERTY_VALUE, fromX, toX, interpolator); 1051 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 1052 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 1053 } else { 1054 setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator); 1055 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 1056 } 1057 1058 if (mIsHotseatIconOnTopWhenAligned) { 1059 setter.addFloat(child, VIEW_ALPHA, 0f, 1f, 1060 isToHome 1061 ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f) 1062 : mActivity.getDeviceProfile().isQsbInline 1063 ? Interpolators.clampToProgress(LINEAR, 0f, 1f) 1064 : Interpolators.clampToProgress(LINEAR, 0.84f, 1f)); 1065 } 1066 setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child)); 1067 continue; 1068 } 1069 1070 int recentTaskIndex = -1; 1071 if (isRecentTask) { 1072 if (firstRecentTaskIndex < 0) { 1073 firstRecentTaskIndex = i; 1074 } 1075 recentTaskIndex = i - firstRecentTaskIndex; 1076 } 1077 float positionInHotseat = getPositionInHotseat(taskbarDp.numShownHotseatIcons, child, 1078 mIsRtl, isAllAppsButton, isTaskbarDividerView, 1079 mTaskbarView.isDividerForRecents(), recentTaskIndex); 1080 if (positionInHotseat == ERROR_POSITION_IN_HOTSEAT_NOT_FOUND) continue; 1081 1082 1083 float hotseatIconCenter; 1084 if (launcherDp.shouldAdjustHotseatForBubbleBar(child.getContext(), 1085 bubbleBarHasBubbles())) { 1086 float hotseatAdjustedBorderSpace = 1087 launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext()); 1088 hotseatIconCenter = hotseatPadding.left + hotseatCellSize 1089 + (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat 1090 + hotseatCellSize / 2f; 1091 } else { 1092 hotseatIconCenter = hotseatPadding.left 1093 + (hotseatCellSize + borderSpacing) * positionInHotseat 1094 + hotseatCellSize / 2f; 1095 } 1096 hotseatIconCenter += hotseatNavBarTranslationX; 1097 float childCenter = (child.getLeft() + child.getRight()) / 2f; 1098 childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX( 1099 INDEX_TASKBAR_PINNING_ANIM).getValue(); 1100 float toX = hotseatIconCenter - childCenter; 1101 if (child instanceof Reorderable) { 1102 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 1103 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 1104 MULTI_PROPERTY_VALUE, toX, interpolator); 1105 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 1106 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 1107 } else { 1108 setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator); 1109 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 1110 } 1111 setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator); 1112 } 1113 1114 AnimatorPlaybackController controller = setter.createPlaybackController(); 1115 mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0); 1116 return controller; 1117 } 1118 1119 /** 1120 * Returns the index of the given child relative to its position in hotseat. 1121 * Examples: 1122 * -1 is the item before the first hotseat item. 1123 * -0.5 is between those (e.g. for the divider). 1124 * {@link #ERROR_POSITION_IN_HOTSEAT_NOT_FOUND} if there's no calculation relative to hotseat. 1125 */ 1126 @VisibleForTesting getPositionInHotseat(int numShownHotseatIcons, View child, boolean isRtl, boolean isAllAppsButton, boolean isTaskbarDividerView, boolean isDividerForRecents, int recentTaskIndex)1127 float getPositionInHotseat(int numShownHotseatIcons, View child, boolean isRtl, 1128 boolean isAllAppsButton, boolean isTaskbarDividerView, boolean isDividerForRecents, 1129 int recentTaskIndex) { 1130 float positionInHotseat; 1131 // Note that there is no All Apps button in the hotseat, 1132 // this position is only used as it's convenient for animation purposes. 1133 float allAppsButtonPositionInHotseat = isRtl 1134 // Right after all hotseat items. 1135 // [HHHHHH]|[>A<] 1136 ? numShownHotseatIcons 1137 // Right before all hotseat items. 1138 // [>A<]|[HHHHHH] 1139 : -1; 1140 // Note that there are no recent tasks in the hotseat, 1141 // this position is only used as it's convenient for animation purposes. 1142 float firstRecentTaskPositionInHotseat = isRtl 1143 // After all hotseat icons and All Apps button. 1144 // [HHHHHH][A]|[>R<R] 1145 ? numShownHotseatIcons + 1 1146 // Right after all hotseat items. 1147 // [A][HHHHHH]|[>R<R] 1148 : numShownHotseatIcons; 1149 if (isAllAppsButton) { 1150 positionInHotseat = allAppsButtonPositionInHotseat; 1151 } else if (isTaskbarDividerView) { 1152 // Note that there is no taskbar divider view in the hotseat, 1153 // this position is only used as it's convenient for animation purposes. 1154 float relativePosition = isDividerForRecents 1155 ? firstRecentTaskPositionInHotseat 1156 : allAppsButtonPositionInHotseat; 1157 positionInHotseat = relativePosition > 0 1158 ? relativePosition - DIVIDER_VIEW_POSITION_OFFSET 1159 : relativePosition + DIVIDER_VIEW_POSITION_OFFSET; 1160 } else if (child.getTag() instanceof ItemInfo) { 1161 positionInHotseat = ((ItemInfo) child.getTag()).screenId; 1162 } else if (recentTaskIndex >= 0) { 1163 positionInHotseat = firstRecentTaskPositionInHotseat + recentTaskIndex; 1164 } else { 1165 Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); 1166 return ERROR_POSITION_IN_HOTSEAT_NOT_FOUND; 1167 } 1168 return positionInHotseat; 1169 } 1170 bubbleBarHasBubbles()1171 private boolean bubbleBarHasBubbles() { 1172 return mBubbleControllers != null 1173 && mBubbleControllers.bubbleBarViewController.hasBubbles(); 1174 } 1175 onRotationChanged(DeviceProfile deviceProfile)1176 public void onRotationChanged(DeviceProfile deviceProfile) { 1177 if (!mControllers.uiController.isIconAlignedWithHotseat()) { 1178 // We only translate on rotation when icon is aligned with hotseat 1179 return; 1180 } 1181 int taskbarWindowSize; 1182 if (mActivity.isPhoneMode()) { 1183 taskbarWindowSize = mActivity.getResources().getDimensionPixelSize( 1184 mActivity.isThreeButtonNav() 1185 ? R.dimen.taskbar_phone_size 1186 : R.dimen.taskbar_stashed_size); 1187 } else { 1188 taskbarWindowSize = deviceProfile.taskbarHeight + deviceProfile.getTaskbarOffsetY(); 1189 } 1190 mActivity.setTaskbarWindowSize(taskbarWindowSize); 1191 mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY()); 1192 } 1193 getContent()1194 public LauncherBindableItemsContainer getContent() { 1195 return mModelCallbacks; 1196 } 1197 1198 /** 1199 * Returns the first icon to match the given parameter, in priority from: 1200 * 1) Icons directly on Taskbar 1201 * 2) FolderIcon of the Folder containing the given icon 1202 * 3) All Apps button 1203 */ getFirstIconMatch(Predicate<ItemInfo> matcher)1204 public View getFirstIconMatch(Predicate<ItemInfo> matcher) { 1205 View icon = mModelCallbacks.getFirstMatch(matcher, ItemInfoMatcher.forFolderMatch(matcher)); 1206 return icon != null ? icon : mTaskbarView.getAllAppsButtonContainer(); 1207 } 1208 1209 /** 1210 * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's 1211 * touch bounds. 1212 */ isEventOverAnyItem(MotionEvent ev)1213 public boolean isEventOverAnyItem(MotionEvent ev) { 1214 return mTaskbarView.isEventOverAnyItem(ev); 1215 } 1216 1217 /** Called when there's a change in running apps to update the UI. */ commitRunningAppsToUI()1218 public void commitRunningAppsToUI() { 1219 mModelCallbacks.commitRunningAppsToUI(); 1220 if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() 1221 && !mActivity.isTransientTaskbar() 1222 && mTaskbarView.getLayoutTransition() == null) { 1223 // Set up after the first commit so that the initial recents do not animate (janky). 1224 mTaskbarView.setLayoutTransition(createLayoutTransitionForRunningApps()); 1225 } 1226 } 1227 createLayoutTransitionForRunningApps()1228 private LayoutTransition createLayoutTransitionForRunningApps() { 1229 LayoutTransition layoutTransition = new LayoutTransition(); 1230 layoutTransition.setDuration(TRANSITION_DEFAULT_DURATION); 1231 layoutTransition.addTransitionListener(new TransitionListener() { 1232 1233 @Override 1234 public void startTransition( 1235 LayoutTransition transition, ViewGroup container, View view, int type) { 1236 if (type == APPEARING) { 1237 view.setAlpha(0f); 1238 view.setScaleX(0f); 1239 view.setScaleY(0f); 1240 if (view instanceof BubbleTextView btv) { 1241 // Defer so that app is mostly scaled in before showing indicator. 1242 btv.setLineIndicatorAnimStartDelay(APPEARING_LINE_INDICATOR_ANIM_DELAY); 1243 } 1244 } else if (type == DISAPPEARING && view instanceof BubbleTextView btv) { 1245 // Running state updates happen after removing this view, so update it here. 1246 updateRunningState(btv); 1247 } 1248 } 1249 1250 @Override 1251 public void endTransition( 1252 LayoutTransition transition, ViewGroup container, View view, int type) { 1253 if (type == APPEARING && view instanceof BubbleTextView btv) { 1254 btv.setLineIndicatorAnimStartDelay(0); 1255 } 1256 } 1257 }); 1258 1259 // Appearing. 1260 AnimatorSet appearingSet = new AnimatorSet(); 1261 Animator appearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); 1262 appearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 1263 (float) TRANSITION_FADE_IN_DURATION / TRANSITION_DEFAULT_DURATION)); 1264 Animator appearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 0f, 1f); 1265 appearingScaleAnimator.setInterpolator(EMPHASIZED); 1266 appearingSet.playTogether(appearingAlphaAnimator, appearingScaleAnimator); 1267 layoutTransition.setAnimator(APPEARING, appearingSet); 1268 layoutTransition.setStartDelay(APPEARING, TRANSITION_DELAY); 1269 1270 // Disappearing. 1271 AnimatorSet disappearingSet = new AnimatorSet(); 1272 Animator disappearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); 1273 disappearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 1274 (float) TRANSITION_DELAY / TRANSITION_DEFAULT_DURATION, 1275 (float) (TRANSITION_DELAY + TRANSITION_FADE_OUT_DURATION) 1276 / TRANSITION_DEFAULT_DURATION)); 1277 Animator disappearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 1f, 0f); 1278 disappearingScaleAnimator.setInterpolator(EMPHASIZED); 1279 disappearingSet.playTogether(disappearingAlphaAnimator, disappearingScaleAnimator); 1280 layoutTransition.setAnimator(DISAPPEARING, disappearingSet); 1281 1282 // Change transitions. 1283 FloatProperty<View> translateXPinning = new FloatProperty<>("translateXPinning") { 1284 @Override 1285 public void setValue(View view, float value) { 1286 getTranslationXForPinning(view).setValue(value); 1287 } 1288 1289 @Override 1290 public Float get(View view) { 1291 return getTranslationXForPinning(view).getValue(); 1292 } 1293 1294 private MultiProperty getTranslationXForPinning(View view) { 1295 return ((Reorderable) view).getTranslateDelegate() 1296 .getTranslationX(INDEX_TASKBAR_PINNING_ANIM); 1297 } 1298 }; 1299 AnimatorSet changeSet = new AnimatorSet(); 1300 changeSet.playTogether( 1301 layoutTransition.getAnimator(CHANGE_APPEARING), 1302 ObjectAnimator.ofFloat(null, translateXPinning, 0f, 1f)); 1303 1304 // Change appearing. 1305 layoutTransition.setAnimator(CHANGE_APPEARING, changeSet); 1306 layoutTransition.setInterpolator(CHANGE_APPEARING, EMPHASIZED); 1307 1308 // Change disappearing. 1309 layoutTransition.setAnimator(CHANGE_DISAPPEARING, changeSet); 1310 layoutTransition.setInterpolator(CHANGE_DISAPPEARING, EMPHASIZED); 1311 layoutTransition.setStartDelay(CHANGE_DISAPPEARING, TRANSITION_DELAY); 1312 1313 return layoutTransition; 1314 } 1315 1316 /** 1317 * To be called when the given Task is updated, so that we can tell TaskbarView to also update. 1318 * @param task The Task whose e.g. icon changed. 1319 */ onTaskUpdated(Task task)1320 public void onTaskUpdated(Task task) { 1321 // Find the icon view(s) that changed. 1322 for (View view : mTaskbarView.getIconViews()) { 1323 if (view instanceof BubbleTextView btv 1324 && view.getTag() instanceof GroupTask groupTask) { 1325 if (groupTask.containsTask(task.key.id)) { 1326 mTaskbarView.applyGroupTaskToBubbleTextView(btv, groupTask); 1327 } 1328 } else if (view instanceof TaskbarOverflowView overflowButton) { 1329 overflowButton.updateTaskIsShown(task); 1330 } 1331 } 1332 } 1333 1334 @Override dumpLogs(String prefix, PrintWriter pw)1335 public void dumpLogs(String prefix, PrintWriter pw) { 1336 pw.println(prefix + "TaskbarViewController:"); 1337 1338 mTaskbarIconAlpha.dump( 1339 prefix + "\t", 1340 pw, 1341 "mTaskbarIconAlpha", 1342 "ALPHA_INDEX_HOME", 1343 "ALPHA_INDEX_KEYGUARD", 1344 "ALPHA_INDEX_STASH", 1345 "ALPHA_INDEX_RECENTS_DISABLED", 1346 "ALPHA_INDEX_NOTIFICATION_EXPANDED", 1347 "ALPHA_INDEX_ASSISTANT_INVOKED", 1348 "ALPHA_INDEX_SMALL_SCREEN"); 1349 1350 mModelCallbacks.dumpLogs(prefix + "\t", pw); 1351 } 1352 createTaskbarIconsShiftAnimator(float translationX)1353 private ObjectAnimator createTaskbarIconsShiftAnimator(float translationX) { 1354 ObjectAnimator animator = mIconsTranslationXForNavbar.animateToValue(translationX); 1355 animator.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS); 1356 animator.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS); 1357 animator.setInterpolator(EMPHASIZED); 1358 return animator; 1359 } 1360 } 1361