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