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