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.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
19 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
20 import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
21 import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
22 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
23 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
24 
25 import android.animation.Animator;
26 import android.animation.AnimatorSet;
27 import android.window.RemoteTransition;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 
32 import com.android.launcher3.DeviceProfile;
33 import com.android.launcher3.Flags;
34 import com.android.launcher3.LauncherState;
35 import com.android.launcher3.QuickstepTransitionManager;
36 import com.android.launcher3.Utilities;
37 import com.android.launcher3.anim.AnimatedFloat;
38 import com.android.launcher3.logging.InstanceId;
39 import com.android.launcher3.logging.InstanceIdSequence;
40 import com.android.launcher3.model.data.ItemInfo;
41 import com.android.launcher3.statehandlers.DesktopVisibilityController;
42 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
43 import com.android.launcher3.uioverrides.QuickstepLauncher;
44 import com.android.launcher3.util.DisplayController;
45 import com.android.launcher3.util.MultiPropertyFactory;
46 import com.android.launcher3.util.OnboardingPrefs;
47 import com.android.quickstep.HomeVisibilityState;
48 import com.android.quickstep.LauncherActivityInterface;
49 import com.android.quickstep.RecentsAnimationCallbacks;
50 import com.android.quickstep.SystemUiProxy;
51 import com.android.quickstep.util.GroupTask;
52 import com.android.quickstep.util.TISBindHelper;
53 import com.android.quickstep.views.RecentsView;
54 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
55 
56 import java.io.PrintWriter;
57 import java.util.Arrays;
58 
59 /**
60  * A data source which integrates with a Launcher instance
61  */
62 public class LauncherTaskbarUIController extends TaskbarUIController {
63 
64     private static final String TAG = "TaskbarUIController";
65 
66     public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0;
67     public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
68     public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
69     public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
70 
71     public static final int DISPLAY_PROGRESS_COUNT = 4;
72 
73     private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
74             this::onInAppDisplayProgressChanged);
75     private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
76             new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
77                     AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
78 
79     private final QuickstepLauncher mLauncher;
80     private final HomeVisibilityState mHomeState;
81 
82     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
83             dp -> {
84                 onStashedInAppChanged(dp);
85                 if (mControllers != null && mControllers.taskbarViewController != null) {
86                     mControllers.taskbarViewController.onRotationChanged(dp);
87                 }
88             };
89     private final HomeVisibilityState.VisibilityChangeListener  mVisibilityChangeListener =
90             this::onLauncherVisibilityChanged;
91 
92     // Initialized in init.
93     private final TaskbarLauncherStateController
94             mTaskbarLauncherStateController = new TaskbarLauncherStateController();
95 
LauncherTaskbarUIController(QuickstepLauncher launcher)96     public LauncherTaskbarUIController(QuickstepLauncher launcher) {
97         mLauncher = launcher;
98         mHomeState =  SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
99     }
100 
101     @Override
init(TaskbarControllers taskbarControllers)102     protected void init(TaskbarControllers taskbarControllers) {
103         super.init(taskbarControllers);
104 
105         mTaskbarLauncherStateController.init(mControllers, mLauncher,
106                 mControllers.getSharedState().sysuiStateFlags);
107 
108         mLauncher.setTaskbarUIController(this);
109         mHomeState.addListener(mVisibilityChangeListener);
110         onLauncherVisibilityChanged(
111                 Flags.useActivityOverlay()
112                         ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(),
113                 true /* fromInit */);
114 
115         onStashedInAppChanged(mLauncher.getDeviceProfile());
116         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
117 
118         // Restore the in-app display progress from before Taskbar was recreated.
119         float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues;
120         // Make a copy of the previous progress to set since updating the multiprop will update
121         // the property which also calls onInAppDisplayProgressChanged() which writes the current
122         // values into the shared state
123         prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length);
124         for (int i = 0; i < prevProgresses.length; i++) {
125             mTaskbarInAppDisplayProgressMultiProp.get(i).setValue(prevProgresses[i]);
126         }
127     }
128 
129     @Override
onDestroy()130     protected void onDestroy() {
131         super.onDestroy();
132         onLauncherVisibilityChanged(false);
133         mTaskbarLauncherStateController.onDestroy();
134 
135         mLauncher.setTaskbarUIController(null);
136         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
137         mHomeState.removeListener(mVisibilityChangeListener);
138     }
139 
onInAppDisplayProgressChanged()140     private void onInAppDisplayProgressChanged() {
141         if (mControllers != null) {
142             // Update our shared state so we can restore it if taskbar gets recreated.
143             for (int i = 0; i < DISPLAY_PROGRESS_COUNT; i++) {
144                 mControllers.getSharedState().inAppDisplayProgressMultiPropValues[i] =
145                         mTaskbarInAppDisplayProgressMultiProp.get(i).getValue();
146             }
147             // Ensure nav buttons react to our latest state if necessary.
148             mControllers.navbarButtonsViewController.updateNavButtonTranslationY();
149         }
150     }
151 
152     @Override
isTaskbarTouchable()153     protected boolean isTaskbarTouchable() {
154         return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
155                 && mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat());
156     }
157 
setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)158     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
159         mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim(
160                 shouldDelayLauncherStateAnim);
161     }
162 
163     /**
164      * Adds the Launcher resume animator to the given animator set.
165      *
166      * This should be used to run a Launcher resume animation whose progress matches a
167      * swipe progress.
168      *
169      * @param placeholderDuration a placeholder duration to be used to ensure all full-length
170      *                            sub-animations are properly coordinated. This duration should not
171      *                            actually be used since this animation tracks a swipe progress.
172      */
addLauncherVisibilityChangedAnimation(AnimatorSet animation, int placeholderDuration)173     protected void addLauncherVisibilityChangedAnimation(AnimatorSet animation,
174             int placeholderDuration) {
175         animation.play(onLauncherVisibilityChanged(
176                 /* isResumed= */ true,
177                 /* fromInit= */ false,
178                 /* startAnimation= */ false,
179                 placeholderDuration));
180     }
181 
182     /**
183      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
184      */
185     @Override
onLauncherVisibilityChanged(boolean isVisible)186     public void onLauncherVisibilityChanged(boolean isVisible) {
187         onLauncherVisibilityChanged(isVisible, false /* fromInit */);
188     }
189 
onLauncherVisibilityChanged(boolean isVisible, boolean fromInit)190     private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) {
191         onLauncherVisibilityChanged(
192                 isVisible,
193                 fromInit,
194                 /* startAnimation= */ true,
195                 DisplayController.isTransientTaskbar(mLauncher)
196                         ? TRANSIENT_TASKBAR_TRANSITION_DURATION
197                         : (!isVisible
198                                 ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
199                                 : QuickstepTransitionManager.getTaskbarToHomeDuration()));
200     }
201 
202     @Nullable
onLauncherVisibilityChanged( boolean isVisible, boolean fromInit, boolean startAnimation, int duration)203     private Animator onLauncherVisibilityChanged(
204             boolean isVisible, boolean fromInit, boolean startAnimation, int duration) {
205         // Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
206         // avoid updating taskbar state in that situation (when it's non-interactive -- or
207         // "background") to avoid premature animations.
208         if (ENABLE_SHELL_TRANSITIONS && isVisible
209                 && mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)
210                 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
211             return null;
212         }
213 
214         DesktopVisibilityController desktopController =
215                 LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
216         if (!enableDesktopWindowingWallpaperActivity()
217                 && desktopController != null
218                 && desktopController.areDesktopTasksVisible()) {
219             // TODO: b/333533253 - Remove after flag rollout
220             isVisible = false;
221         }
222 
223         mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
224         if (fromInit) {
225             duration = 0;
226         }
227         return mTaskbarLauncherStateController.applyState(duration, startAnimation);
228     }
229 
230     @Override
onStateTransitionCompletedAfterSwipeToHome(LauncherState state)231     public void onStateTransitionCompletedAfterSwipeToHome(LauncherState state) {
232         mTaskbarLauncherStateController.onStateTransitionCompletedAfterSwipeToHome(state);
233     }
234 
235     @Override
refreshResumedState()236     public void refreshResumedState() {
237         onLauncherVisibilityChanged(Flags.useActivityOverlay()
238                 ? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed());
239     }
240 
241     @Override
adjustHotseatForBubbleBar(boolean isBubbleBarVisible)242     public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {
243         if (mLauncher.getHotseat() != null) {
244             mLauncher.getHotseat().adjustForBubbleBar(isBubbleBarVisible);
245         }
246     }
247 
248     /**
249      * Create Taskbar animation when going from an app to Launcher as part of recents transition.
250      * @param toState If known, the state we will end up in when reaching Launcher.
251      * @param callbacks callbacks to track the recents animation lifecycle. The state change is
252      *                 automatically reset once the recents animation finishes
253      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)254     public Animator createAnimToLauncher(@NonNull LauncherState toState,
255             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
256         return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
257     }
258 
updateTaskbarLauncherStateGoingHome()259     public void updateTaskbarLauncherStateGoingHome() {
260         mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, true);
261         mTaskbarLauncherStateController.applyState();
262     }
263 
isDraggingItem()264     public boolean isDraggingItem() {
265         return mControllers.taskbarDragController.isDragging();
266     }
267 
268     @Override
onStashedInAppChanged()269     protected void onStashedInAppChanged() {
270         onStashedInAppChanged(mLauncher.getDeviceProfile());
271     }
272 
onStashedInAppChanged(DeviceProfile deviceProfile)273     private void onStashedInAppChanged(DeviceProfile deviceProfile) {
274         boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
275         deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
276     }
277 
278     /**
279      * Starts a Taskbar EDU flow, if the user should see one upon launching an application.
280      */
showEduOnAppLaunch()281     public void showEduOnAppLaunch() {
282         if (!shouldShowEduOnAppLaunch()) {
283             // Called in case the edu finishes and search edu is still pending
284             mControllers.taskbarEduTooltipController.maybeShowSearchEdu();
285             return;
286         }
287 
288         // Persistent features EDU tooltip.
289         if (!DisplayController.isTransientTaskbar(mLauncher)) {
290             mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
291             return;
292         }
293 
294         // Transient swipe EDU tooltip.
295         mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
296     }
297 
298     /** Will make the next onRecentsAnimationFinished() animation a no-op. */
setSkipNextRecentsAnimEnd()299     public void setSkipNextRecentsAnimEnd() {
300         mTaskbarLauncherStateController.setSkipNextRecentsAnimEnd();
301     }
302 
303     /**
304      * Returns {@code true} if a Taskbar education should be shown on application launch.
305      */
shouldShowEduOnAppLaunch()306     public boolean shouldShowEduOnAppLaunch() {
307         if (Utilities.isRunningInTestHarness()) {
308             return false;
309         }
310 
311         // Persistent features EDU tooltip.
312         if (!DisplayController.isTransientTaskbar(mLauncher)) {
313             return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher);
314         }
315 
316         // Transient swipe EDU tooltip.
317         return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES;
318     }
319 
320     @Override
onTaskbarIconLaunched(ItemInfo item)321     public void onTaskbarIconLaunched(ItemInfo item) {
322         super.onTaskbarIconLaunched(item);
323         InstanceId instanceId = new InstanceIdSequence().newInstanceId();
324         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
325                 instanceId);
326     }
327 
328     /**
329      * Animates Taskbar elements during a transition to a Launcher state that should use in-app
330      * layouts.
331      *
332      * @param progress [0, 1]
333      *                 0 => use home layout
334      *                 1 => use in-app layout
335      */
onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex)336     public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) {
337         mTaskbarInAppDisplayProgressMultiProp.get(progressIndex).setValue(progress);
338         if (mControllers == null) {
339             // This method can be called before init() is called.
340             return;
341         }
342         if (mControllers.uiController.isIconAlignedWithHotseat()
343                 && !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
344             // Only animate the nav buttons while home and not animating home, otherwise let
345             // the TaskbarViewController handle it.
346             mControllers.navbarButtonsViewController
347                     .getTaskbarNavButtonTranslationYForInAppDisplay()
348                     .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
349                             * mTaskbarInAppDisplayProgress.value);
350             mControllers.navbarButtonsViewController
351                     .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
352         }
353     }
354 
355     /** Returns true iff any in-app display progress > 0. */
shouldUseInAppLayout()356     public boolean shouldUseInAppLayout() {
357         return mTaskbarInAppDisplayProgress.value > 0;
358     }
359 
isBubbleBarEnabled()360     public boolean isBubbleBarEnabled() {
361         return BubbleBarController.isBubbleBarEnabled();
362     }
363 
364     /** Whether the bubble bar has any bubbles. */
hasBubbles()365     public boolean hasBubbles() {
366         if (mControllers == null) {
367             return false;
368         }
369         if (mControllers.bubbleControllers.isEmpty()) {
370             return false;
371         }
372         return mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
373     }
374 
375     @Override
onExpandPip()376     public void onExpandPip() {
377         super.onExpandPip();
378         mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, false);
379         mTaskbarLauncherStateController.applyState();
380     }
381 
382     @Override
updateStateForSysuiFlags(@ystemUiStateFlags long sysuiFlags)383     public void updateStateForSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
384         mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags);
385     }
386 
387     @Override
isIconAlignedWithHotseat()388     public boolean isIconAlignedWithHotseat() {
389         return mTaskbarLauncherStateController.isIconAlignedWithHotseat();
390     }
391 
392     @Override
isHotseatIconOnTopWhenAligned()393     public boolean isHotseatIconOnTopWhenAligned() {
394         return mTaskbarLauncherStateController.isInHotseatOnTopStates()
395                 && mTaskbarInAppDisplayProgressMultiProp.get(MINUS_ONE_PAGE_PROGRESS_INDEX)
396                     .getValue() == 0;
397     }
398 
399     @Override
isInOverviewUi()400     protected boolean isInOverviewUi() {
401         return mTaskbarLauncherStateController.isInOverviewUi();
402     }
403 
404     @Override
canToggleHomeAllApps()405     protected boolean canToggleHomeAllApps() {
406         return mLauncher.isResumed()
407                 && !mTaskbarLauncherStateController.isInOverviewUi()
408                 && !mLauncher.areDesktopTasksVisible();
409     }
410 
411     @Override
getRecentsView()412     public RecentsView getRecentsView() {
413         return mLauncher.getOverviewPanel();
414     }
415 
416     @Override
launchSplitTasks( @onNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition)417     public void launchSplitTasks(
418             @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
419         mLauncher.launchSplitTasks(groupTask, remoteTransition);
420     }
421 
422     @Override
onIconLayoutBoundsChanged()423     protected void onIconLayoutBoundsChanged() {
424         mTaskbarLauncherStateController.resetIconAlignment();
425     }
426 
427     @Nullable
428     @Override
getTISBindHelper()429     protected TISBindHelper getTISBindHelper() {
430         return mLauncher.getTISBindHelper();
431     }
432 
433     @Override
dumpLogs(String prefix, PrintWriter pw)434     public void dumpLogs(String prefix, PrintWriter pw) {
435         super.dumpLogs(prefix, pw);
436 
437         pw.println(String.format("%s\tTaskbar in-app display progress: %.2f", prefix,
438                 mTaskbarInAppDisplayProgress.value));
439         mTaskbarInAppDisplayProgressMultiProp.dump(
440                 prefix + "\t\t",
441                 pw,
442                 "mTaskbarInAppDisplayProgressMultiProp",
443                 "MINUS_ONE_PAGE_PROGRESS_INDEX",
444                 "ALL_APPS_PAGE_PROGRESS_INDEX",
445                 "WIDGETS_PAGE_PROGRESS_INDEX",
446                 "SYSUI_SURFACE_PROGRESS_INDEX");
447 
448         mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
449     }
450 
451     @Override
getTaskbarUIControllerName()452     protected String getTaskbarUIControllerName() {
453         return "LauncherTaskbarUIController";
454     }
455 }
456