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.Flags.enableScalingRevealHomeAnimation; 21 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 22 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 23 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 24 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 25 import static com.android.launcher3.Utilities.mapRange; 26 import static com.android.launcher3.anim.AnimatedFloat.VALUE; 27 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; 28 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; 29 import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning; 30 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT; 31 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT; 32 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 33 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM; 34 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM; 35 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM; 36 37 import android.animation.Animator; 38 import android.animation.AnimatorSet; 39 import android.animation.ObjectAnimator; 40 import android.animation.ValueAnimator; 41 import android.annotation.NonNull; 42 import android.graphics.Rect; 43 import android.util.Log; 44 import android.view.MotionEvent; 45 import android.view.View; 46 import android.view.animation.Interpolator; 47 48 import androidx.annotation.Nullable; 49 import androidx.core.view.OneShotPreDrawListener; 50 51 import com.android.app.animation.Interpolators; 52 import com.android.launcher3.BubbleTextView; 53 import com.android.launcher3.DeviceProfile; 54 import com.android.launcher3.LauncherAppState; 55 import com.android.launcher3.R; 56 import com.android.launcher3.Reorderable; 57 import com.android.launcher3.Utilities; 58 import com.android.launcher3.anim.AlphaUpdateListener; 59 import com.android.launcher3.anim.AnimatedFloat; 60 import com.android.launcher3.anim.AnimatorPlaybackController; 61 import com.android.launcher3.anim.PendingAnimation; 62 import com.android.launcher3.anim.RevealOutlineAnimation; 63 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 64 import com.android.launcher3.config.FeatureFlags; 65 import com.android.launcher3.model.data.ItemInfo; 66 import com.android.launcher3.util.DisplayController; 67 import com.android.launcher3.util.ItemInfoMatcher; 68 import com.android.launcher3.util.LauncherBindableItemsContainer; 69 import com.android.launcher3.util.MultiPropertyFactory; 70 import com.android.launcher3.util.MultiTranslateDelegate; 71 import com.android.launcher3.util.MultiValueAlpha; 72 import com.android.launcher3.views.IconButtonView; 73 74 import java.io.PrintWriter; 75 import java.util.Set; 76 import java.util.function.Predicate; 77 78 /** 79 * Handles properties/data collection, then passes the results to TaskbarView to render. 80 */ 81 public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController { 82 83 private static final String TAG = "TaskbarViewController"; 84 85 private static final Runnable NO_OP = () -> { }; 86 87 public static final int ALPHA_INDEX_HOME = 0; 88 public static final int ALPHA_INDEX_KEYGUARD = 1; 89 public static final int ALPHA_INDEX_STASH = 2; 90 public static final int ALPHA_INDEX_RECENTS_DISABLED = 3; 91 public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4; 92 public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5; 93 public static final int ALPHA_INDEX_SMALL_SCREEN = 6; 94 private static final int NUM_ALPHA_CHANNELS = 7; 95 96 private final TaskbarActivityContext mActivity; 97 private final TaskbarView mTaskbarView; 98 private final MultiValueAlpha mTaskbarIconAlpha; 99 private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale); 100 private final AnimatedFloat mTaskbarIconTranslationYForHome = new AnimatedFloat( 101 this::updateTranslationY); 102 private final AnimatedFloat mTaskbarIconTranslationYForStash = new AnimatedFloat( 103 this::updateTranslationY); 104 105 private final AnimatedFloat mTaskbarIconScaleForPinning = new AnimatedFloat( 106 this::updateTaskbarIconsScale); 107 108 private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat( 109 this::updateTaskbarIconTranslationXForPinning); 110 111 private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat( 112 this::updateTranslationY); 113 114 private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener = 115 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) 116 -> updateTaskbarIconTranslationXForPinning(); 117 118 119 private AnimatedFloat mTaskbarNavButtonTranslationY; 120 private AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay; 121 private float mTaskbarIconTranslationYForSwipe; 122 private float mTaskbarIconTranslationYForSpringOnStash; 123 124 private int mTaskbarBottomMargin; 125 private final int mStashedHandleHeight; 126 127 private final TaskbarModelCallbacks mModelCallbacks; 128 129 // Initialized in init. 130 private TaskbarControllers mControllers; 131 132 // Animation to align icons with Launcher, created lazily. This allows the controller to be 133 // active only during the animation and does not need to worry about layout changes. 134 private AnimatorPlaybackController mIconAlignControllerLazy = null; 135 private Runnable mOnControllerPreCreateCallback = NO_OP; 136 137 // Stored here as signals to determine if the mIconAlignController needs to be recreated. 138 private boolean mIsHotseatIconOnTopWhenAligned; 139 private boolean mIsStashed; 140 141 private final DeviceProfile.OnDeviceProfileChangeListener mDeviceProfileChangeListener = 142 dp -> commitRunningAppsToUI(); 143 144 private final boolean mIsRtl; 145 146 private final DeviceProfile mTransientTaskbarDp; 147 private final DeviceProfile mPersistentTaskbarDp; 148 149 private final int mTransientIconSize; 150 private final int mPersistentIconSize; 151 152 private final float mTaskbarLeftRightMargin; 153 TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView)154 public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) { 155 mActivity = activity; 156 mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile(); 157 mPersistentTaskbarDp = mActivity.getPersistentTaskbarDeviceProfile(); 158 mTransientIconSize = mTransientTaskbarDp.taskbarIconSize; 159 mPersistentIconSize = mPersistentTaskbarDp.taskbarIconSize; 160 mTaskbarView = taskbarView; 161 mTaskbarIconAlpha = new MultiValueAlpha(mTaskbarView, NUM_ALPHA_CHANNELS); 162 mTaskbarIconAlpha.setUpdateVisibility(true); 163 mModelCallbacks = TaskbarModelCallbacksFactory.newInstance(mActivity) 164 .create(mActivity, mTaskbarView); 165 mTaskbarBottomMargin = activity.getDeviceProfile().taskbarBottomMargin; 166 mStashedHandleHeight = activity.getResources() 167 .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height); 168 169 mIsRtl = Utilities.isRtl(mTaskbarView.getResources()); 170 mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize( 171 R.dimen.transient_taskbar_padding); 172 173 } 174 init(TaskbarControllers controllers)175 public void init(TaskbarControllers controllers) { 176 mControllers = controllers; 177 mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create( 178 mActivity, mControllers, mTaskbarView)); 179 mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode() 180 ? mActivity.getResources().getDimensionPixelSize(R.dimen.taskbar_phone_size) 181 : mActivity.getDeviceProfile().taskbarHeight; 182 183 mTaskbarIconScaleForStash.updateValue(1f); 184 float pinningValue = DisplayController.isTransientTaskbar(mActivity) 185 ? PINNING_TRANSIENT 186 : PINNING_PERSISTENT; 187 mTaskbarIconScaleForPinning.updateValue(pinningValue); 188 mTaskbarIconTranslationYForPinning.updateValue(pinningValue); 189 mTaskbarIconTranslationXForPinning.updateValue(pinningValue); 190 191 mModelCallbacks.init(controllers); 192 if (mActivity.isUserSetupComplete()) { 193 // Only load the callbacks if user setup is completed 194 LauncherAppState.getInstance(mActivity).getModel().addCallbacksAndLoad(mModelCallbacks); 195 } 196 mTaskbarNavButtonTranslationY = 197 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY(); 198 mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController 199 .getTaskbarNavButtonTranslationYForInAppDisplay(); 200 201 mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 202 203 if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) { 204 // This gets modified in NavbarButtonsViewController, but the initial value it reads 205 // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here 206 mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN) 207 .animateToValue(mActivity.isPhoneButtonNavMode() ? 0 : 1).start(); 208 } 209 if (enableTaskbarPinning()) { 210 mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener); 211 } 212 } 213 214 /** 215 * Announcement for Accessibility when Taskbar stashes/unstashes. 216 */ announceForAccessibility()217 public void announceForAccessibility() { 218 mTaskbarView.announceAccessibilityChanges(); 219 } 220 onDestroy()221 public void onDestroy() { 222 if (enableTaskbarPinning()) { 223 mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener); 224 } 225 LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks); 226 mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener); 227 mModelCallbacks.unregisterListeners(); 228 } 229 areIconsVisible()230 public boolean areIconsVisible() { 231 return mTaskbarView.areIconsVisible(); 232 } 233 getTaskbarIconAlpha()234 public MultiPropertyFactory<View> getTaskbarIconAlpha() { 235 return mTaskbarIconAlpha; 236 } 237 238 /** 239 * Should be called when the recents button is disabled, so we can hide Taskbar icons as well. 240 */ setRecentsButtonDisabled(boolean isDisabled)241 public void setRecentsButtonDisabled(boolean isDisabled) { 242 // TODO: check TaskbarStashController#supportsStashing(), to stash instead of setting alpha. 243 mTaskbarIconAlpha.get(ALPHA_INDEX_RECENTS_DISABLED).setValue(isDisabled ? 0 : 1); 244 } 245 246 /** 247 * Sets OnClickListener and OnLongClickListener for the given view. 248 */ setClickAndLongClickListenersForIcon(View icon)249 public void setClickAndLongClickListenersForIcon(View icon) { 250 mTaskbarView.setClickAndLongClickListenersForIcon(icon); 251 } 252 253 /** 254 * Adds one time pre draw listener to the Taskbar view, it is called before 255 * drawing a frame and invoked only once 256 * @param listener callback that will be invoked before drawing the next frame 257 */ addOneTimePreDrawListener(@onNull Runnable listener)258 public void addOneTimePreDrawListener(@NonNull Runnable listener) { 259 OneShotPreDrawListener.add(mTaskbarView, listener); 260 } 261 getIconLayoutBounds()262 public Rect getIconLayoutBounds() { 263 return mTaskbarView.getIconLayoutBounds(); 264 } 265 getIconLayoutWidth()266 public int getIconLayoutWidth() { 267 return mTaskbarView.getIconLayoutWidth(); 268 } 269 getIconViews()270 public View[] getIconViews() { 271 return mTaskbarView.getIconViews(); 272 } 273 274 @Nullable getAllAppsButtonView()275 public View getAllAppsButtonView() { 276 return mTaskbarView.getAllAppsButtonView(); 277 } 278 getTaskbarIconScaleForStash()279 public AnimatedFloat getTaskbarIconScaleForStash() { 280 return mTaskbarIconScaleForStash; 281 } 282 getTaskbarIconTranslationYForStash()283 public AnimatedFloat getTaskbarIconTranslationYForStash() { 284 return mTaskbarIconTranslationYForStash; 285 } 286 getTaskbarIconScaleForPinning()287 public AnimatedFloat getTaskbarIconScaleForPinning() { 288 return mTaskbarIconScaleForPinning; 289 } 290 getTaskbarIconTranslationXForPinning()291 public AnimatedFloat getTaskbarIconTranslationXForPinning() { 292 return mTaskbarIconTranslationXForPinning; 293 } 294 getTaskbarIconTranslationYForPinning()295 public AnimatedFloat getTaskbarIconTranslationYForPinning() { 296 return mTaskbarIconTranslationYForPinning; 297 } 298 299 /** 300 * Applies scale properties for the entire TaskbarView (rather than individual icons). 301 */ updateScale()302 private void updateScale() { 303 float scale = mTaskbarIconScaleForStash.value; 304 mTaskbarView.setScaleX(scale); 305 mTaskbarView.setScaleY(scale); 306 } 307 308 /** 309 * Applies scale properties for the taskbar icons 310 */ updateTaskbarIconsScale()311 private void updateTaskbarIconsScale() { 312 float scale = mTaskbarIconScaleForPinning.value; 313 View[] iconViews = mTaskbarView.getIconViews(); 314 315 float finalScale; 316 if (mControllers.getSharedState().startTaskbarVariantIsTransient) { 317 finalScale = mapRange(scale, 1f, ((float) mPersistentIconSize / mTransientIconSize)); 318 } else { 319 finalScale = mapRange(scale, ((float) mTransientIconSize / mPersistentIconSize), 1f); 320 } 321 322 for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) { 323 iconViews[iconIndex].setScaleX(finalScale); 324 iconViews[iconIndex].setScaleY(finalScale); 325 } 326 } 327 328 /** 329 * Animate away taskbar icon notification dots during the taskbar pinning animation. 330 */ animateAwayNotificationDotsDuringTaskbarPinningAnimation()331 public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() { 332 for (View iconView : mTaskbarView.getIconViews()) { 333 if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) { 334 ((BubbleTextView) iconView).animateDotScale(0); 335 } 336 } 337 } 338 updateTaskbarIconTranslationXForPinning()339 private void updateTaskbarIconTranslationXForPinning() { 340 View[] iconViews = mTaskbarView.getIconViews(); 341 float scale = mTaskbarIconTranslationXForPinning.value; 342 float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension( 343 mTaskbarView.getAllAppsButtonTranslationXOffset(true)); 344 float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension( 345 mTaskbarView.getAllAppsButtonTranslationXOffset(false)); 346 347 float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset, 348 persistentTaskbarAllAppsOffset); 349 350 // no x translation required when all apps button is the only icon in taskbar. 351 if (iconViews.length <= 1) { 352 allAppIconTranslateRange = 0f; 353 } 354 355 if (mIsRtl) { 356 allAppIconTranslateRange *= -1; 357 } 358 359 if (mActivity.isThreeButtonNav()) { 360 ((IconButtonView) mTaskbarView.getAllAppsButtonView()) 361 .setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange); 362 return; 363 } 364 365 float taskbarCenterX = 366 mTaskbarView.getLeft() + (mTaskbarView.getRight() - mTaskbarView.getLeft()) / 2.0f; 367 368 float finalMarginScale = mapRange(scale, 0f, mTransientIconSize - mPersistentIconSize); 369 370 float halfIconCount = iconViews.length / 2.0f; 371 for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) { 372 View iconView = iconViews[iconIndex]; 373 MultiTranslateDelegate translateDelegate = 374 ((Reorderable) iconView).getTranslateDelegate(); 375 float iconCenterX = 376 iconView.getLeft() + (iconView.getRight() - iconView.getLeft()) / 2.0f; 377 if (iconCenterX <= taskbarCenterX) { 378 translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue( 379 finalMarginScale * (halfIconCount - iconIndex)); 380 } else { 381 translateDelegate.getTranslationX(INDEX_TASKBAR_PINNING_ANIM).setValue( 382 -finalMarginScale * (iconIndex - halfIconCount)); 383 } 384 385 if (iconView.equals(mTaskbarView.getAllAppsButtonView())) { 386 ((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon( 387 allAppIconTranslateRange); 388 } 389 } 390 } 391 392 /** 393 * Calculates visual taskbar view width. 394 */ getCurrentVisualTaskbarWidth()395 public float getCurrentVisualTaskbarWidth() { 396 if (mTaskbarView.getIconViews().length == 0) { 397 return 0; 398 } 399 400 View[] iconViews = mTaskbarView.getIconViews(); 401 402 int leftIndex = mActivity.getDeviceProfile().isQsbInline && !mIsRtl ? 1 : 0; 403 int rightIndex = mActivity.getDeviceProfile().isQsbInline && mIsRtl 404 ? iconViews.length - 2 405 : iconViews.length - 1; 406 407 float left = iconViews[leftIndex].getX(); 408 float right = iconViews[rightIndex].getRight() + iconViews[rightIndex].getTranslationX(); 409 410 return right - left + (2 * mTaskbarLeftRightMargin); 411 } 412 413 /** 414 * Sets the translation of the TaskbarView during the swipe up gesture. 415 */ setTranslationYForSwipe(float transY)416 public void setTranslationYForSwipe(float transY) { 417 mTaskbarIconTranslationYForSwipe = transY; 418 updateTranslationY(); 419 } 420 421 /** 422 * Sets the translation of the TaskbarView during the spring on stash animation. 423 */ setTranslationYForStash(float transY)424 public void setTranslationYForStash(float transY) { 425 mTaskbarIconTranslationYForSpringOnStash = transY; 426 updateTranslationY(); 427 } 428 updateTranslationY()429 private void updateTranslationY() { 430 mTaskbarView.setTranslationY(mTaskbarIconTranslationYForHome.value 431 + mTaskbarIconTranslationYForStash.value 432 + mTaskbarIconTranslationYForSwipe 433 + getTaskbarIconTranslationYForPinningValue() 434 + mTaskbarIconTranslationYForSpringOnStash); 435 } 436 437 /** 438 * Computes translation y for taskbar pinning. 439 */ getTaskbarIconTranslationYForPinningValue()440 private float getTaskbarIconTranslationYForPinningValue() { 441 if (mControllers.getSharedState() == null) return 0f; 442 443 float scale = mTaskbarIconTranslationYForPinning.value; 444 float taskbarIconTranslationYForPinningValue; 445 446 // transY is calculated here by adding/subtracting the taskbar bottom margin 447 // aligning the icon bound to be at bottom of current taskbar view and then 448 // finally placing the icon in the middle of new taskbar background height. 449 if (mControllers.getSharedState().startTaskbarVariantIsTransient) { 450 float transY = 451 mTransientTaskbarDp.taskbarBottomMargin + (mTransientTaskbarDp.taskbarHeight 452 - mTaskbarView.getIconLayoutBounds().bottom) 453 - (mPersistentTaskbarDp.taskbarHeight 454 - mTransientTaskbarDp.taskbarIconSize) / 2f; 455 taskbarIconTranslationYForPinningValue = mapRange(scale, 0f, transY); 456 } else { 457 float transY = 458 -mTransientTaskbarDp.taskbarBottomMargin + (mPersistentTaskbarDp.taskbarHeight 459 - mTaskbarView.getIconLayoutBounds().bottom) 460 - (mTransientTaskbarDp.taskbarHeight 461 - mTransientTaskbarDp.taskbarIconSize) / 2f; 462 taskbarIconTranslationYForPinningValue = mapRange(scale, transY, 0f); 463 } 464 return taskbarIconTranslationYForPinningValue; 465 } 466 createRevealAnimForView(View view, boolean isStashed, float newWidth, boolean isQsb, boolean dispatchOnAnimationStart)467 private ValueAnimator createRevealAnimForView(View view, boolean isStashed, float newWidth, 468 boolean isQsb, boolean dispatchOnAnimationStart) { 469 Rect viewBounds = new Rect(0, 0, view.getWidth(), view.getHeight()); 470 int centerY = viewBounds.centerY(); 471 int halfHandleHeight = mStashedHandleHeight / 2; 472 final int top = centerY - halfHandleHeight; 473 final int bottom = centerY + halfHandleHeight; 474 475 final int left; 476 final int right; 477 // QSB will crop from the 'start' whereas all other icons will crop from the center. 478 if (isQsb) { 479 if (mIsRtl) { 480 right = viewBounds.right; 481 left = (int) (right - newWidth); 482 } else { 483 left = viewBounds.left; 484 right = (int) (left + newWidth); 485 } 486 } else { 487 int widthDelta = (int) ((viewBounds.width() - newWidth) / 2); 488 489 left = viewBounds.left + widthDelta; 490 right = viewBounds.right - widthDelta; 491 } 492 493 Rect stashedRect = new Rect(left, top, right, bottom); 494 // QSB radius can be > 0 since it does not have any UI elements outside of it bounds. 495 float radius = isQsb 496 ? viewBounds.height() / 2f 497 : 0f; 498 float stashedRadius = stashedRect.height() / 2f; 499 500 ValueAnimator reveal = new RoundedRectRevealOutlineProvider(radius, 501 stashedRadius, viewBounds, stashedRect) 502 .createRevealAnimator(view, !isStashed, 0); 503 // SUW animation does not dispatch animation start until *after* the animation is complete. 504 // In order to work properly, the reveal animation start needs to be called immediately. 505 if (dispatchOnAnimationStart) { 506 for (Animator.AnimatorListener listener : reveal.getListeners()) { 507 listener.onAnimationStart(reveal); 508 } 509 } 510 return reveal; 511 } 512 getTaskbarDividerView()513 public View getTaskbarDividerView() { 514 return mTaskbarView.getTaskbarDividerView(); 515 } 516 517 /** Updates which icons are marked as running given the Set of currently running packages. */ updateIconViewsRunningStates(Set<String> runningPackages, Set<String> minimizedPackages)518 public void updateIconViewsRunningStates(Set<String> runningPackages, 519 Set<String> minimizedPackages) { 520 for (View iconView : getIconViews()) { 521 if (iconView instanceof BubbleTextView btv) { 522 btv.updateRunningState( 523 getRunningAppState(btv.getTargetPackageName(), runningPackages, 524 minimizedPackages)); 525 } 526 } 527 } 528 getRunningAppState( String packageName, Set<String> runningPackages, Set<String> minimizedPackages)529 private BubbleTextView.RunningAppState getRunningAppState( 530 String packageName, 531 Set<String> runningPackages, 532 Set<String> minimizedPackages) { 533 if (minimizedPackages.contains(packageName)) { 534 return BubbleTextView.RunningAppState.MINIMIZED; 535 } 536 if (runningPackages.contains(packageName)) { 537 return BubbleTextView.RunningAppState.RUNNING; 538 } 539 return BubbleTextView.RunningAppState.NOT_RUNNING; 540 } 541 542 /** 543 * Defers any updates to the UI for the setup wizard animation. 544 */ setDeferUpdatesForSUW(boolean defer)545 public void setDeferUpdatesForSUW(boolean defer) { 546 mModelCallbacks.setDeferUpdatesForSUW(defer); 547 } 548 549 /** 550 * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the icon shape 551 * and size. 552 * @param as The AnimatorSet to add all animations to. 553 * @param isStashed When true, the icon crops vertically to the size of the stashed handle. 554 * When false, the reverse happens. 555 * @param duration The duration of the animation. 556 * @param interpolator The interpolator to use for all animations. 557 */ addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, Interpolator interpolator, boolean dispatchOnAnimationStart)558 public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, 559 Interpolator interpolator, boolean dispatchOnAnimationStart) { 560 AnimatorSet reveal = new AnimatorSet(); 561 562 Rect stashedBounds = new Rect(); 563 mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds); 564 565 int numIcons = mTaskbarView.getChildCount(); 566 float newChildWidth = stashedBounds.width() / (float) numIcons; 567 568 // All children move the same y-amount since they will be cropped to the same centerY. 569 float croppedTransY = mTaskbarView.getIconTouchSize() - stashedBounds.height(); 570 571 for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) { 572 View child = mTaskbarView.getChildAt(i); 573 boolean isQsb = child == mTaskbarView.getQsb(); 574 575 // Crop the icons to/from the nav handle shape. 576 reveal.play(createRevealAnimForView(child, isStashed, newChildWidth, isQsb, 577 dispatchOnAnimationStart).setDuration(duration)); 578 579 // Translate the icons to/from their locations as the "nav handle." 580 581 // All of the Taskbar icons will overlap the entirety of the stashed handle 582 // And the QSB, if inline, will overlap part of stashed handle as well. 583 float currentPosition = isQsb ? child.getX() : child.getLeft(); 584 float newPosition = stashedBounds.left + (newChildWidth * i); 585 final float croppedTransX; 586 // We look at 'left' and 'right' values to ensure that the children stay within the 587 // bounds of the stashed handle since the new width only occurs at the end of the anim. 588 if (currentPosition > newPosition) { 589 float newRight = stashedBounds.right - (newChildWidth 590 * (numIcons - 1 - i)); 591 croppedTransX = -(currentPosition + child.getWidth() - newRight); 592 } else { 593 croppedTransX = newPosition - currentPosition; 594 } 595 float[] transX = isStashed 596 ? new float[] {croppedTransX} 597 : new float[] {croppedTransX, 0}; 598 float[] transY = isStashed 599 ? new float[] {croppedTransY} 600 : new float[] {croppedTransY, 0}; 601 602 if (child instanceof Reorderable) { 603 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 604 605 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM), 606 MULTI_PROPERTY_VALUE, transX) 607 .setDuration(duration)); 608 reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), 609 MULTI_PROPERTY_VALUE, transY)); 610 as.addListener(forEndCallback(() -> 611 mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0))); 612 } else { 613 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_X, transX) 614 .setDuration(duration)); 615 reveal.play(ObjectAnimator.ofFloat(child, VIEW_TRANSLATE_Y, transY)); 616 as.addListener(forEndCallback(() -> { 617 child.setTranslationX(0); 618 child.setTranslationY(0); 619 })); 620 } 621 } 622 623 reveal.setInterpolator(interpolator); 624 as.play(reveal); 625 } 626 627 /** 628 * Sets the Taskbar icon alignment relative to Launcher hotseat icons 629 * @param alignmentRatio [0, 1] 630 * 0 => not aligned 631 * 1 => fully aligned 632 */ setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp)633 public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) { 634 if (mActivity.isPhoneMode()) { 635 mIconAlignControllerLazy = null; 636 return; 637 } 638 639 boolean isHotseatIconOnTopWhenAligned = 640 mControllers.uiController.isHotseatIconOnTopWhenAligned(); 641 boolean isStashed = mControllers.taskbarStashController.isStashed(); 642 // Re-create animation when mIsHotseatIconOnTopWhenAligned or mIsStashed changes. 643 if (mIconAlignControllerLazy == null 644 || mIsHotseatIconOnTopWhenAligned != isHotseatIconOnTopWhenAligned 645 || mIsStashed != isStashed) { 646 mIsHotseatIconOnTopWhenAligned = isHotseatIconOnTopWhenAligned; 647 mIsStashed = isStashed; 648 mIconAlignControllerLazy = createIconAlignmentController(launcherDp); 649 } 650 mIconAlignControllerLazy.setPlayFraction(alignmentRatio); 651 if (alignmentRatio <= 0 || alignmentRatio >= 1) { 652 // Cleanup lazy controller so that it is created again in next animation 653 mIconAlignControllerLazy = null; 654 } 655 } 656 657 /** Resets the icon alignment controller so that it can be recreated again later. */ resetIconAlignmentController()658 void resetIconAlignmentController() { 659 mIconAlignControllerLazy = null; 660 } 661 662 /** 663 * Creates an animation for aligning the Taskbar icons with the provided Launcher device profile 664 */ createIconAlignmentController(DeviceProfile launcherDp)665 private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) { 666 PendingAnimation setter = new PendingAnimation(100); 667 mOnControllerPreCreateCallback.run(); 668 DeviceProfile taskbarDp = mActivity.getDeviceProfile(); 669 Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity); 670 boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity); 671 672 float scaleUp = ((float) launcherDp.iconSizePx) / taskbarDp.taskbarIconSize; 673 int borderSpacing = launcherDp.hotseatBorderSpace; 674 int hotseatCellSize = DeviceProfile.calculateCellWidth( 675 launcherDp.availableWidthPx - hotseatPadding.left - hotseatPadding.right, 676 borderSpacing, 677 launcherDp.numShownHotseatIcons); 678 679 boolean isToHome = mControllers.uiController.isIconAlignedWithHotseat(); 680 // If Hotseat is not the top element, Taskbar should maintain in-app state as it fades out, 681 // or fade in while already in in-app state. 682 Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME; 683 684 int offsetY = launcherDp.getTaskbarOffsetY(); 685 setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator); 686 setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator); 687 setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator); 688 689 int collapsedHeight = mActivity.getDefaultTaskbarWindowSize(); 690 int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY); 691 setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowSize( 692 anim.getAnimatedFraction() > 0 ? expandedHeight : collapsedHeight)); 693 694 mTaskbarBottomMargin = isTransientTaskbar 695 ? mTransientTaskbarDp.taskbarBottomMargin 696 : mPersistentTaskbarDp.taskbarBottomMargin; 697 698 for (int i = 0; i < mTaskbarView.getChildCount(); i++) { 699 View child = mTaskbarView.getChildAt(i); 700 boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView(); 701 boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView(); 702 if (!mIsHotseatIconOnTopWhenAligned) { 703 // When going to home, the EMPHASIZED interpolator in TaskbarLauncherStateController 704 // plays iconAlignment to 1 really fast, therefore moving the fading towards the end 705 // to avoid icons disappearing rather than fading out visually. 706 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f)); 707 } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get()) 708 || (isTaskbarDividerView && enableTaskbarPinning())) { 709 if (!isToHome 710 && mIsHotseatIconOnTopWhenAligned 711 && mIsStashed) { 712 // Prevent All Apps icon from appearing when going from hotseat to nav handle. 713 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0f, 0f)); 714 } else if (enableScalingRevealHomeAnimation()) { 715 // Tighten clamp so that these icons do not linger as the spring settles. 716 setter.setViewAlpha(child, 0, 717 isToHome 718 ? Interpolators.clampToProgress(LINEAR, 0f, 0.07f) 719 : Interpolators.clampToProgress(LINEAR, 0.93f, 1f)); 720 } else { 721 setter.setViewAlpha(child, 0, 722 isToHome 723 ? Interpolators.clampToProgress(LINEAR, 0f, 0.17f) 724 : Interpolators.clampToProgress(LINEAR, 0.72f, 0.84f)); 725 } 726 } 727 728 if (child == mTaskbarView.getQsb()) { 729 boolean isRtl = Utilities.isRtl(child.getResources()); 730 float hotseatIconCenter = isRtl 731 ? launcherDp.widthPx - hotseatPadding.right + borderSpacing 732 + launcherDp.hotseatQsbWidth / 2f 733 : hotseatPadding.left - borderSpacing - launcherDp.hotseatQsbWidth / 2f; 734 float childCenter = (child.getLeft() + child.getRight()) / 2f; 735 childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX( 736 INDEX_TASKBAR_PINNING_ANIM).getValue(); 737 float halfQsbIconWidthDiff = 738 (launcherDp.hotseatQsbWidth - taskbarDp.taskbarIconSize) / 2f; 739 float scale = ((float) taskbarDp.taskbarIconSize) 740 / launcherDp.hotseatQsbVisualHeight; 741 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator); 742 743 float fromX = isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff; 744 float toX = hotseatIconCenter - childCenter; 745 if (child instanceof Reorderable) { 746 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 747 748 setter.addFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 749 MULTI_PROPERTY_VALUE, fromX, toX, interpolator); 750 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 751 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 752 } else { 753 setter.addFloat(child, VIEW_TRANSLATE_X, fromX, toX, interpolator); 754 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 755 } 756 757 if (mIsHotseatIconOnTopWhenAligned) { 758 setter.addFloat(child, VIEW_ALPHA, 0f, 1f, 759 isToHome 760 ? Interpolators.clampToProgress(LINEAR, 0f, 0.35f) 761 : mActivity.getDeviceProfile().isQsbInline 762 ? Interpolators.clampToProgress(LINEAR, 0f, 1f) 763 : Interpolators.clampToProgress(LINEAR, 0.84f, 1f)); 764 } 765 setter.addOnFrameListener(animator -> AlphaUpdateListener.updateVisibility(child)); 766 continue; 767 } 768 769 float positionInHotseat; 770 if (isAllAppsButton) { 771 // Note that there is no All Apps button in the hotseat, 772 // this position is only used as its convenient for animation purposes. 773 positionInHotseat = Utilities.isRtl(child.getResources()) 774 ? taskbarDp.numShownHotseatIcons 775 : -1; 776 } else if (isTaskbarDividerView) { 777 // Note that there is no taskbar divider view in the hotseat, 778 // this position is only used as its convenient for animation purposes. 779 positionInHotseat = Utilities.isRtl(child.getResources()) 780 ? taskbarDp.numShownHotseatIcons - 0.5f 781 : -0.5f; 782 } else if (child.getTag() instanceof ItemInfo) { 783 positionInHotseat = ((ItemInfo) child.getTag()).screenId; 784 } else { 785 Log.w(TAG, "Unsupported view found in createIconAlignmentController, v=" + child); 786 continue; 787 } 788 789 float hotseatAdjustedBorderSpace = 790 launcherDp.getHotseatAdjustedBorderSpaceForBubbleBar(child.getContext()); 791 float hotseatIconCenter; 792 if (bubbleBarHasBubbles() && hotseatAdjustedBorderSpace != 0) { 793 hotseatIconCenter = hotseatPadding.left + hotseatCellSize 794 + (hotseatCellSize + hotseatAdjustedBorderSpace) * positionInHotseat 795 + hotseatCellSize / 2f; 796 } else { 797 hotseatIconCenter = hotseatPadding.left 798 + (hotseatCellSize + borderSpacing) * positionInHotseat 799 + hotseatCellSize / 2f; 800 } 801 float childCenter = (child.getLeft() + child.getRight()) / 2f; 802 childCenter += ((Reorderable) child).getTranslateDelegate().getTranslationX( 803 INDEX_TASKBAR_PINNING_ANIM).getValue(); 804 float toX = hotseatIconCenter - childCenter; 805 if (child instanceof Reorderable) { 806 MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate(); 807 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM), 808 MULTI_PROPERTY_VALUE, toX, interpolator); 809 setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM), 810 MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator); 811 } else { 812 setter.setFloat(child, VIEW_TRANSLATE_X, toX, interpolator); 813 setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator); 814 } 815 setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator); 816 } 817 818 AnimatorPlaybackController controller = setter.createPlaybackController(); 819 mOnControllerPreCreateCallback = () -> controller.setPlayFraction(0); 820 return controller; 821 } 822 bubbleBarHasBubbles()823 private boolean bubbleBarHasBubbles() { 824 return mControllers.bubbleControllers.isPresent() 825 && mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles(); 826 } 827 onRotationChanged(DeviceProfile deviceProfile)828 public void onRotationChanged(DeviceProfile deviceProfile) { 829 if (!mControllers.uiController.isIconAlignedWithHotseat()) { 830 // We only translate on rotation when icon is aligned with hotseat 831 return; 832 } 833 int taskbarWindowSize; 834 if (mActivity.isPhoneMode()) { 835 taskbarWindowSize = mActivity.getResources().getDimensionPixelSize( 836 mActivity.isThreeButtonNav() 837 ? R.dimen.taskbar_phone_size 838 : R.dimen.taskbar_stashed_size); 839 } else { 840 taskbarWindowSize = deviceProfile.taskbarHeight + deviceProfile.getTaskbarOffsetY(); 841 } 842 mActivity.setTaskbarWindowSize(taskbarWindowSize); 843 mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY()); 844 } 845 846 /** 847 * Maps the given operator to all the top-level children of TaskbarView. 848 */ mapOverItems(LauncherBindableItemsContainer.ItemOperator op)849 public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) { 850 mTaskbarView.mapOverItems(op); 851 } 852 853 /** 854 * Returns the first icon to match the given parameter, in priority from: 855 * 1) Icons directly on Taskbar 856 * 2) FolderIcon of the Folder containing the given icon 857 * 3) All Apps button 858 */ getFirstIconMatch(Predicate<ItemInfo> matcher)859 public View getFirstIconMatch(Predicate<ItemInfo> matcher) { 860 Predicate<ItemInfo> collectionMatcher = ItemInfoMatcher.forFolderMatch(matcher); 861 return mTaskbarView.getFirstMatch(matcher, collectionMatcher); 862 } 863 864 /** 865 * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's 866 * touch bounds. 867 */ isEventOverAnyItem(MotionEvent ev)868 public boolean isEventOverAnyItem(MotionEvent ev) { 869 return mTaskbarView.isEventOverAnyItem(ev); 870 } 871 872 @Override dumpLogs(String prefix, PrintWriter pw)873 public void dumpLogs(String prefix, PrintWriter pw) { 874 pw.println(prefix + "TaskbarViewController:"); 875 876 mTaskbarIconAlpha.dump( 877 prefix + "\t", 878 pw, 879 "mTaskbarIconAlpha", 880 "ALPHA_INDEX_HOME", 881 "ALPHA_INDEX_KEYGUARD", 882 "ALPHA_INDEX_STASH", 883 "ALPHA_INDEX_RECENTS_DISABLED", 884 "ALPHA_INDEX_NOTIFICATION_EXPANDED", 885 "ALPHA_INDEX_ASSISTANT_INVOKED", 886 "ALPHA_INDEX_IME_BUTTON_NAV", 887 "ALPHA_INDEX_SMALL_SCREEN"); 888 889 mModelCallbacks.dumpLogs(prefix + "\t", pw); 890 } 891 892 /** Called when there's a change in running apps to update the UI. */ commitRunningAppsToUI()893 public void commitRunningAppsToUI() { 894 mModelCallbacks.commitRunningAppsToUI(); 895 } 896 897 /** Call TaskbarModelCallbacks to update running apps. */ updateRunningApps()898 public void updateRunningApps() { 899 mModelCallbacks.updateRunningApps(); 900 } 901 902 } 903