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