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