1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.launcher3.taskbar; 17 18 import static com.android.app.animation.Interpolators.FINAL_FRAME; 19 import static com.android.app.animation.Interpolators.LINEAR; 20 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 21 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 22 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 23 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 24 import static com.android.launcher3.Utilities.squaredHypot; 25 import static com.android.launcher3.anim.AnimatedFloat.VALUE; 26 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP; 28 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode; 29 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode; 30 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 31 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM; 32 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM; 33 34 import android.animation.Animator; 35 import android.animation.AnimatorSet; 36 import android.animation.ObjectAnimator; 37 import android.animation.ValueAnimator; 38 import android.annotation.NonNull; 39 import android.graphics.Rect; 40 import android.util.Log; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.view.animation.Interpolator; 44 45 import androidx.annotation.Nullable; 46 import androidx.core.graphics.ColorUtils; 47 import androidx.core.view.OneShotPreDrawListener; 48 49 import com.android.app.animation.Interpolators; 50 import com.android.launcher3.DeviceProfile; 51 import com.android.launcher3.LauncherAppState; 52 import com.android.launcher3.R; 53 import com.android.launcher3.Reorderable; 54 import com.android.launcher3.Utilities; 55 import com.android.launcher3.anim.AlphaUpdateListener; 56 import com.android.launcher3.anim.AnimatedFloat; 57 import com.android.launcher3.anim.AnimatorPlaybackController; 58 import com.android.launcher3.anim.PendingAnimation; 59 import com.android.launcher3.anim.RevealOutlineAnimation; 60 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 61 import com.android.launcher3.config.FeatureFlags; 62 import com.android.launcher3.icons.ThemedIconDrawable; 63 import com.android.launcher3.model.data.ItemInfo; 64 import com.android.launcher3.util.ItemInfoMatcher; 65 import com.android.launcher3.util.LauncherBindableItemsContainer; 66 import com.android.launcher3.util.MultiPropertyFactory; 67 import com.android.launcher3.util.MultiTranslateDelegate; 68 import com.android.launcher3.util.MultiValueAlpha; 69 70 import java.io.PrintWriter; 71 import java.util.function.Predicate; 72 73 /** 74 * Handles properties/data collection, then passes the results to TaskbarView to render. 75 */ 76 public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController { 77 78 private static final String TAG = TaskbarViewController.class.getSimpleName(); 79 80 private static final Runnable NO_OP = () -> { }; 81 82 public static final int ALPHA_INDEX_HOME = 0; 83 public static final int ALPHA_INDEX_KEYGUARD = 1; 84 public static final int ALPHA_INDEX_STASH = 2; 85 public static final int ALPHA_INDEX_RECENTS_DISABLED = 3; 86 public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4; 87 public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5; 88 public static final int ALPHA_INDEX_SMALL_SCREEN = 6; 89 private static final int NUM_ALPHA_CHANNELS = 7; 90 91 private static final float TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE = 0.30f; 92 93 private final TaskbarActivityContext mActivity; 94 private final TaskbarView mTaskbarView; 95 private final MultiValueAlpha mTaskbarIconAlpha; 96 private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); 97 private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( 98 this::updateTranslationY); 99 private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( 100 this::updateTranslationY); 101 private AnimatedFloat mTaskbarNavButtonTranslationY; 102 private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay; 103 private float mTaskbarIconTranslationYForSwipe; 104 private float mTaskbarIconTranslationYForSpringOnStash; 105 106 private final int mTaskbarBottomMargin; 107 private final int mStashedHandleHeight; 108 private final int mLauncherThemedIconsBackgroundColor; 109 private final int mTaskbarThemedIconsBackgroundColor; 110 111 /** Progress from {@code 0} for Launcher's color to {@code 1} for Taskbar's color. */ 112 private final AnimatedFloat mThemedIconsBackgroundProgress = new AnimatedFloat( 113 this::updateIconsBackground); 114 115 private final TaskbarModelCallbacks mModelCallbacks; 116 117 // Initialized in init. 118 private TaskbarControllers mControllers; 119 120 // Animation to align icons with Launcher, created lazily. This allows the controller to be 121 // active only during the animation and does not need to worry about layout changes. 122 private AnimatorPlaybackController mIconAlignControllerLazy = null; 123 private Runnable mOnControllerPreCreateCallback = NO_OP; 124 125 // Stored here as signals to determine if the mIconAlignController needs to be recreated. 126 private boolean mIsHotseatIconOnTopWhenAligned; 127 private boolean mIsStashed; 128 129 private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener = 130 dp -> commitRunningAppsToUI(); 131 132 private final boolean mIsRtl; 133 TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView)134 public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { 135 mActivity = activity; 136 mTaskbarView = taskbarView; 137 mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS); 138 mTaskbarIconAlpha.setUpdateVisibility(true); 139 mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity) 140 .create(mActivity, mTaskbarView); 141 mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin; 142 mStashedHandleHeight = activity.getResources() 143 .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height); 144 mLauncherThemedIconsBackgroundColor = ThemedIconDrawable.getColors(mActivity)[0]; 145 if (!Utilities.isDarkTheme(mActivity)) { 146 mTaskbarThemedIconsBackgroundColor = mLauncherThemedIconsBackgroundColor; 147 } else { 148 // Increase luminance for dark themed icons given they are on a dark Taskbar background. 149 float[] colorHSL = new float[3]; 150 ColorUtils.colorToHSL(mLauncherThemedIconsBackgroundColor, colorHSL); 151 colorHSL[2] = TASKBAR_DARK_THEME_ICONS_BACKGROUND_LUMINANCE; 152 mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL); 153 } 154 mIsRtl = Utilities.isRtl(mTaskbarView.getResources()); 155 } 156 init(TaskbarControllers controllers)157 public void init(TaskbarControllers controllers) { 158 mControllers = controllers; 159 mTaskbarView.init(new TaskbarViewCallbacks()); 160 mTaskbarView.getLayoutParams().height = isPhoneMode(mActivity.getDeviceProfile()) 161 ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_size) 162 : mActivity.getDeviceProfile().taskbarHeight; 163 164 mTaskbarIconScaleForStash.updateValue(1f); 165 166 mModelCallbacks.init(controllers); 167 if (mActivity.isUserSetupComplete()) { 168 // Only load the callbacks if user setup is completed 169 LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks); 170 } 171 mTaskbarNavButtonTranslationY = 172 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY(); 173 mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController 174 .getTaskbarNavButtonTranslationYForInAppDisplay(); 175 176 mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 177 178 if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) { 179 // This gets modified in NavbarButtonsViewController, but the initial value it reads 180 // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here 181 mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN) 182 .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start(); 183 } 184 } 185 186 /** 187 * Announcement for Accessibility when Taskbar stashes/unstashes. 188 */ announceForAccessibility()189 public void announceForAccessibility() { 190 mTaskbarView.announceAccessibilityChanges(); 191 } 192 onDestroy()193 public void onDestroy() { 194 LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); 195 mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 196 mModelCallbacks.unregisterListeners(); 197 } 198 areIconsVisible()199 public boolean areIconsVisible() { 200 return mTaskbarView.areIconsVisible(); 201 } 202 getTaskbarIconAlpha()203 public MultiPropertyFactory<View> getTaskbarIconAlpha() { 204 return mTaskbarIconAlpha; 205 } 206 207 /** 208 * Should be called when the recents button is disabled, so we can hide Taskbar icons as well. 209 */ setRecentsButtonDisabled(boolean isDisabled)210 public void setRecentsButtonDisabled(boolean isDisabled) { 211 // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha. 212 mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1); 213 } 214 215 /** 216 * Sets OnClickListener and OnLongClickListener for the given view. 217 */ setClickAndLongClickListenersForIcon(View icon)218 public void setClickAndLongClickListenersForIcon(View icon) { 219 mTaskbarView.setClickAndLongClickListenersForIcon(icon); 220 } 221 222 /** 223 * Adds one time pre draw listener to the Taskbar view, it is called before 224 * drawing a frame and invoked only once 225 * @param listener callback that will be invoked before drawing the next frame 226 */ addOneTimePreDrawListener(@onNull Runnable listener)227 public void addOneTimePreDrawListener(@NonNull Runnable listener) { 228 OneShotPreDrawListener.add(mTaskbarView, listener); 229 } 230 getIconLayoutBounds()231 public Rect getIconLayoutBounds() { 232 return mTaskbarView.getIconLayoutBounds(); 233 } 234 getIconLayoutWidth()235 public int getIconLayoutWidth() { 236 return mTaskbarView.getIconLayoutWidth(); 237 } 238 getIconViews()239 public View[] getIconViews() { 240 return mTaskbarView.getIconViews(); 241 } 242 243 @Nullable getAllAppsButtonView()244 public View getAllAppsButtonView() { 245 return mTaskbarView.getAllAppsButtonView(); 246 } 247 getTaskbarIconScaleForStash()248 public AnimatedFloat getTaskbarIconScaleForStash() { 249 return mTaskbarIconScaleForStash; 250 } 251 getTaskbarIconTranslationYForStash()252 public AnimatedFloat getTaskbarIconTranslationYForStash() { 253 return mTaskbarIconTranslationYForStash; 254 } 255 256 /** 257 * Applies scale properties for the entire TaskbarView (rather than individual icons). 258 */ updateScale()259 private void updateScale() { 260 float scale = mTaskbarIconScaleForStash.value; 261 mTaskbarView.setScaleX(scale); 262 mTaskbarView.setScaleY(scale); 263 } 264 265 /** 266 * Sets the translation of the TaskbarView during the swipe up gesture. 267 */ setTranslationYForSwipe(float transY)268 public void setTranslationYForSwipe(float transY) { 269 mTaskbarIconTranslationYForSwipe = transY; 270 updateTranslationY(); 271 } 272 273 /** 274 * Sets the translation of the TaskbarView during the spring on stash animation. 275 */ setTranslationYForStash(float transY)276 public void setTranslationYForStash(float transY) { 277 mTaskbarIconTranslationYForSpringOnStash = transY; 278 updateTranslationY(); 279 } 280 updateTranslationY()281 private void updateTranslationY() { 282 mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value 283 + mTaskbarIconTranslationYForStash.value 284 + mTaskbarIconTranslationYForSwipe 285 + mTaskbarIconTranslationYForSpringOnStash); 286 } 287 288 /** 289 * Updates the Taskbar's themed icons background according to the progress between in-app/home. 290 */ updateIconsBackground()291 protected void updateIconsBackground() { 292 mTaskbarView.setThemedIconsBackgroundColor( 293 ColorUtils.blendARGB( 294 mLauncherThemedIconsBackgroundColor, 295 mTaskbarThemedIconsBackgroundColor, 296 mThemedIconsBackgroundProgress.value 297 )); 298 } 299 createRevealAnimForView(View view, boolean isStashed, float newWidth, boolean isQsb, boolean dispatchOnAnimationStart)300 private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth, 301 boolean isQsb, boolean dispatchOnAnimationStart) { 302 Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight()); 303 int centerY = viewBounds.centerY(); 304 int halfHandleHeight = mStashedHandleHeight / 2; 305 final int top = centerY - halfHandleHeight; 306 final int bottom = centerY + halfHandleHeight; 307 308 final int left; 309 final int right; 310 // QSB will crop from the 'start' whereas all other icons will crop from the center. 311 if (isQsb) { 312 if (mIsRtl) { 313 right = viewBounds.right; 314 left = (int) (right - newWidth); 315 } else { 316 left = viewBounds.left; 317 right = (int) (left + newWidth); 318 } 319 } else { 320 int widthDelta = (int) ((viewBounds.width() - newWidth) / 2); 321 322 left = viewBounds.left + widthDelta; 323 right = viewBounds.right - widthDelta; 324 } 325 326 Rect stashedRect = new Rect(left, top, right, bottom); 327 // QSB radius can be > 0 since it does not have any UI elements outside of it bounds. 328 float radius = isQsb 329 ? viewBounds.height() / 2f 330 : 0f; 331 float stashedRadius = stashedRect.height() / 2f; 332 333 ValueAnimator reveal = new RoundedRectRevealOutlineProvider(radius, 334 stashedRadius, viewBounds, stashedRect) 335 .createRevealAnimator(view, !isStashed, 0); 336 // SUW animation does not dispatch animation start until *after* the animation is complete. 337 // In order to work properly, the reveal animation start needs to be called immediately. 338 if (dispatchOnAnimationStart) { 339 for (Animator.AnimatorListener listener : reveal.getListeners()) { 340 listener.onAnimationStart(reveal); 341 } 342 } 343 return reveal; 344 } 345 346 /** 347 * Defers any updates to the UI for the setup wizard animation. 348 */ setDeferUpdatesForSUW(boolean defer)349 public void setDeferUpdatesForSUW(boolean defer) { 350 mModelCallbacks.setDeferUpdatesForSUW(defer); 351 } 352 353 /** 354 * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape 355 * and size. 356 * @param as The AnimatorSet to add all animations to. 357 * @param isStashed When true, the icon crops vertically to the size of the stashed handle. 358 * When false, the reverse happens. 359 * @param duration The duration of the animation. 360 * @param interpolator The interpolator to use for all animations. 361 */ addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, Interpolator interpolator, boolean dispatchOnAnimationStart)362 public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, 363 Interpolator interpolator, boolean dispatchOnAnimationStart) { 364 AnimatorSet reveal = new AnimatorSet(); 365 366 Rect stashedBounds = new Rect(); 367 mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds); 368 369 int numIcons = mTaskbarView.getChildCount(); 370 float newChildWidth = stashedBounds.width() / (float) numIcons; 371 372 // All children move the same y-amount since they will be cropped to the same centerY. 373 float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height(); 374 375 for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) { 376 View child = mTaskbarView.getChildAt(i); 377 boolean isQsb = child == mTaskbarView.getQsb(); 378 379 // Crop the icons to/from the nav handle shape. 380 reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb, 381 dispatchOnAnimationStart).setDuration(duration)); 382 383 // Translate the icons to/from their locations as the "nav handle." 384 385 // All of the Taskbar icons will overlap the entirety of the stashed handle 386 // And the QSB, if inline, will overlap part of stashed handle as well. 387 float currentPosition = isQsb ? child.getX() : child.getLeft(); 388 float newPosition = stashedBounds.left + (newChildWidth * i); 389 final float croppedTransX; 390 // We look at 'left' and 'right' values to ensure that the children stay within the 391 // bounds of the stashed handle since the new width only occurs at the end of the anim. 392 if (currentPosition > newPosition) { 393 float newRight = stashedBounds.right - (newChildWidth 394 * (numIcons - 1 - i)); 395 croppedTransX = -(currentPosition + child.getWidth() - newRight); 396 } else { 397 croppedTransX = newPosition - currentPosition; 398 } 399 float[] transX = isStashed 400 ? new float[] {croppedTransX} 401 : new float[] {croppedTransX, 0}; 402 float[] transY = isStashed 403 ? new float[] {croppedTransY} 404 : new float[] {croppedTransY, 0}; 405 406 if (child instanceof Reorderable) { 407 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 408 409 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM), 410 MULTI_PROPERTY_VALUE, transX) 411 .setDuration(duration)); 412 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), 413 MULTI_PROPERTY_VALUE, transY)); 414 as.addListener(forEndCallback(() -> 415 mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0))); 416 } else { 417 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX) 418 .setDuration(duration)); 419 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY)); 420 as.addListener(forEndCallback(() -> { 421 child.setTranslationX(0); 422 child.setTranslationY(0); 423 })); 424 } 425 } 426 427 reveal.setInterpolator(interpolator); 428 as.play(reveal); 429 } 430 431 /** 432 * Sets the Taskbar icon alignment relative to Launcher hotseat icons 433 * @param alignmentRatio [0, 1] 434 * 0 => not aligned 435 * 1 => fully aligned 436 */ setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp)437 public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) { 438 boolean isHotseatIconOnTopWhenAligned = 439 mControllers.uiController.isHotseatIconOnTopWhenAligned(); 440 boolean isStashed = mControllers.taskbarStashController.isStashed(); 441 // Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes. 442 if (mIconAlignControllerLazy == null 443 || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned 444 || mIsStashed != isStashed) { 445 mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned; 446 mIsStashed = isStashed; 447 mIconAlignControllerLazy = createIconAlignmentController(launcherDp); 448 } 449 mIconAlignControllerLazy.setPlayFraction(alignmentRatio); 450 if (alignmentRatio <= 0 || alignmentRatio >= 1) { 451 // Cleanup lazy controller so that it is created again in next animation 452 mIconAlignControllerLazy = null; 453 } 454 } 455 456 /** 457 * Creates an animation for aligning the Taskbar icons with the provided Launcher device profile 458 */ createIconAlignmentController(DeviceProfile launcherDp)459 private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) { 460 PendingAnimation setter = new PendingAnimation(100); 461 if (TaskbarManager.isPhoneButtonNavMode(mActivity)) { 462 // No animation for icons in small-screen 463 return setter.createPlaybackController(); 464 } 465 466 mOnControllerPreCreateCallback.run(); 467 DeviceProfile taskbarDp = mActivity.getDeviceProfile(); 468 Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity); 469 float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize; 470 int borderSpacing = launcherDp.hotseatBorderSpace; 471 int hotseatCellSize = DeviceProfile.calculateCellWidth( 472 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right, 473 borderSpacing, 474 launcherDp.numShownHotseatIcons); 475 476 boolean isToHome = mControllers.uiController.isIconAlignedWithHotseat(); 477 // If Hotseat is not the top element, Taskbar should maintain in-app state as it fades out, 478 // or fade in while already in in-app state. 479 Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME; 480 481 int offsetY = launcherDp.getTaskbarOffsetY(); 482 setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator); 483 setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator); 484 setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator); 485 486 if (Utilities.isDarkTheme(mTaskbarView.getContext())) { 487 setter.addFloat(mThemedIconsBackgroundProgress, VALUE, 1f, 0f, LINEAR); 488 } 489 490 int collapsedHeight = mActivity.getDefaultTaskbarWindowHeight(); 491 int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY); 492 setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowHeight( 493 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight)); 494 495 for (int i = 0; i < mTaskbarView.getChildCount(); i++) { 496 View child = mTaskbarView.getChildAt(i); 497 boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView(); 498 boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView(); 499 if (!mIsHotseatIconOnTopWhenAligned) { 500 // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController 501 // plays iconAlignment to 1 really fast, therefore moving the fading towards the end 502 // to avoid icons disappearing rather than fading out visually. 503 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f)); 504 } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) 505 || (isTaskbarDividerView && FeatureFlags.ENABLE_TASKBAR_PINNING.get())) { 506 if (!isToHome 507 && mIsHotseatIconOnTopWhenAligned 508 && mIsStashed) { 509 // Prevent All Apps icon from appearing when going from hotseat to nav handle. 510 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f)); 511 } else { 512 setter.setViewAlpha(child, 0, 513 isToHome 514 ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f) 515 : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f)); 516 } 517 } 518 519 if (child == mTaskbarView.getQsb()) { 520 boolean isRtl = Utilities.isRtl(child.getResources()); 521 float hotseatIconCenter = isRtl 522 ? launcherDp.widthPx - hotseatPadding.right + borderSpacing 523 + launcherDp.hotseatQsbWidth / 2f 524 : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f; 525 float childCenter = (child.getLeft() + child.getRight()) / 2f; 526 float halfQsbIconWidthDiff = 527 (launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f; 528 float scale = ((float) taskbarDp.taskbarIconSize) 529 / launcherDp.hotseatQsbVisualHeight; 530 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator); 531 532 float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff; 533 float toX = hotseatIconCenter - childCenter; 534 if (child instanceof Reorderable) { 535 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 536 537 setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 538 MULTI_PROPERTY_VALUE, fromX, toX, interpolator); 539 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 540 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 541 } else { 542 setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator); 543 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 544 } 545 546 if (mIsHotseatIconOnTopWhenAligned) { 547 setter.addFloat(child, VIEW_ALPHA, 0f, 1f, 548 isToHome 549 ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f) 550 : mActivity.getDeviceProfile().isQsbInline 551 ? Interpolators.clampToProgress(LINEAR, 0f, 1f) 552 : Interpolators.clampToProgress(LINEAR, 0.84f, 1f)); 553 } 554 setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child)); 555 continue; 556 } 557 558 int positionInHotseat; 559 if (isAllAppsButton) { 560 // Note that there is no All Apps button in the hotseat, this position is only used 561 // as its convenient for animation purposes. 562 positionInHotseat = Utilities.isRtl(child.getResources()) 563 ? taskbarDp.numShownHotseatIcons 564 : -1; 565 } else if (child.getTag() instanceof ItemInfo) { 566 positionInHotseat = ((ItemInfo) child.getTag()).screenId; 567 } else { 568 Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); 569 continue; 570 } 571 572 float hotseatIconCenter = hotseatPadding.left 573 + (hotseatCellSize + borderSpacing) * positionInHotseat 574 + hotseatCellSize / 2f; 575 float childCenter = (child.getLeft() + child.getRight()) / 2f; 576 float toX = hotseatIconCenter - childCenter; 577 if (child instanceof Reorderable) { 578 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 579 580 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 581 MULTI_PROPERTY_VALUE, toX, interpolator); 582 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 583 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 584 } else { 585 setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator); 586 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 587 } 588 setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator); 589 } 590 591 AnimatorPlaybackController controller = setter.createPlaybackController(); 592 mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0); 593 return controller; 594 } 595 onRotationChanged(DeviceProfile deviceProfile)596 public void onRotationChanged(DeviceProfile deviceProfile) { 597 if (!mControllers.uiController.isIconAlignedWithHotseat()) { 598 // We only translate on rotation when icon is aligned with hotseat 599 return; 600 } 601 mActivity.setTaskbarWindowHeight( 602 deviceProfile.taskbarHeight + deviceProfile.getTaskbarOffsetY()); 603 mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY()); 604 } 605 606 /** 607 * Maps the given operator to all the top-level children of TaskbarView. 608 */ mapOverItems(LauncherBindableItemsContainer.ItemOperator op)609 public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) { 610 mTaskbarView.mapOverItems(op); 611 } 612 613 /** 614 * Returns the first icon to match the given parameter, in priority from: 615 * 1) Icons directly on Taskbar 616 * 2) FolderIcon of the Folder containing the given icon 617 * 3) All Apps button 618 */ getFirstIconMatch(Predicate<ItemInfo> matcher)619 public View getFirstIconMatch(Predicate<ItemInfo> matcher) { 620 Predicate<ItemInfo> folderMatcher = ItemInfoMatcher.forFolderMatch(matcher); 621 return mTaskbarView.getFirstMatch(matcher, folderMatcher); 622 } 623 624 /** 625 * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's 626 * touch bounds. 627 */ isEventOverAnyItem(MotionEvent ev)628 public boolean isEventOverAnyItem(MotionEvent ev) { 629 return mTaskbarView.isEventOverAnyItem(ev); 630 } 631 632 @Override dumpLogs(String prefix, PrintWriter pw)633 public void dumpLogs(String prefix, PrintWriter pw) { 634 pw.println(prefix + "TaskbarViewController:"); 635 636 mTaskbarIconAlpha.dump( 637 prefix + "\t", 638 pw, 639 "mTaskbarIconAlpha", 640 "ALPHA_INDEX_HOME", 641 "ALPHA_INDEX_KEYGUARD", 642 "ALPHA_INDEX_STASH", 643 "ALPHA_INDEX_RECENTS_DISABLED", 644 "ALPHA_INDEX_NOTIFICATION_EXPANDED", 645 "ALPHA_INDEX_ASSISTANT_INVOKED", 646 "ALPHA_INDEX_IME_BUTTON_NAV", 647 "ALPHA_INDEX_SMALL_SCREEN"); 648 649 mModelCallbacks.dumpLogs(prefix + "\t", pw); 650 } 651 652 /** Called when there's a change in running apps to update the UI. */ commitRunningAppsToUI()653 public void commitRunningAppsToUI() { 654 mModelCallbacks.commitRunningAppsToUI(); 655 } 656 657 /** Call TaskbarModelCallbacks to update running apps. */ updateRunningApps()658 public void updateRunningApps() { 659 mModelCallbacks.updateRunningApps(); 660 } 661 662 /** 663 * Callbacks for {@link TaskbarView} to interact with its controller. 664 */ 665 public class TaskbarViewCallbacks { 666 private final float mSquaredTouchSlop = Utilities.squaredTouchSlop(mActivity); 667 668 private float mDownX, mDownY; 669 private boolean mCanceledStashHint; 670 getIconOnClickListener()671 public View.OnClickListener getIconOnClickListener() { 672 return mActivity.getItemOnClickListener(); 673 } 674 getAllAppsButtonClickListener()675 public View.OnClickListener getAllAppsButtonClickListener() { 676 return v -> { 677 mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP); 678 mControllers.taskbarAllAppsController.toggle(); 679 }; 680 } 681 getTaskbarDividerLongClickListener()682 public View.OnLongClickListener getTaskbarDividerLongClickListener() { 683 return v -> { 684 mControllers.taskbarPinningController.showPinningView(v); 685 return true; 686 }; 687 } 688 getIconOnLongClickListener()689 public View.OnLongClickListener getIconOnLongClickListener() { 690 return mControllers.taskbarDragController::startDragOnLongClick; 691 } 692 getBackgroundOnLongClickListener()693 public View.OnLongClickListener getBackgroundOnLongClickListener() { 694 return view -> mControllers.taskbarStashController 695 .updateAndAnimateIsManuallyStashedInApp(true); 696 } 697 698 /** Gets the hover listener for the provided icon view. */ getIconOnHoverListener(View icon)699 public View.OnHoverListener getIconOnHoverListener(View icon) { 700 return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon); 701 } 702 703 /** 704 * Get the first chance to handle TaskbarView#onTouchEvent, and return whether we want to 705 * consume the touch so TaskbarView treats it as an ACTION_CANCEL. 706 * TODO(b/270395798): We can remove this entirely once we remove the Transient Taskbar flag. 707 */ onTouchEvent(MotionEvent motionEvent)708 public boolean onTouchEvent(MotionEvent motionEvent) { 709 final float x = motionEvent.getRawX(); 710 final float y = motionEvent.getRawY(); 711 switch (motionEvent.getAction()) { 712 case MotionEvent.ACTION_DOWN: 713 mDownX = x; 714 mDownY = y; 715 mControllers.taskbarStashController.startStashHint(/* animateForward = */ true); 716 mCanceledStashHint = false; 717 break; 718 case MotionEvent.ACTION_MOVE: 719 if (!mCanceledStashHint 720 && squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop) { 721 mControllers.taskbarStashController.startStashHint( 722 /* animateForward= */ false); 723 mCanceledStashHint = true; 724 return true; 725 } 726 break; 727 case MotionEvent.ACTION_UP: 728 case MotionEvent.ACTION_CANCEL: 729 if (!mCanceledStashHint) { 730 mControllers.taskbarStashController.startStashHint( 731 /* animateForward= */ false); 732 } 733 break; 734 } 735 736 return false; 737 } 738 739 /** 740 * Notifies launcher to update icon alignment. 741 */ notifyIconLayoutBoundsChanged()742 public void notifyIconLayoutBoundsChanged() { 743 mControllers.uiController.onIconLayoutBoundsChanged(); 744 } 745 } 746 } 747