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