• 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.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_RESUMED;
22 import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
23 
24 import android.animation.Animator;
25 import android.animation.AnimatorSet;
26 import android.os.RemoteException;
27 import android.util.Log;
28 import android.view.TaskTransitionSpec;
29 import android.view.View;
30 import android.view.WindowManagerGlobal;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.DeviceProfile;
36 import com.android.launcher3.LauncherState;
37 import com.android.launcher3.QuickstepTransitionManager;
38 import com.android.launcher3.R;
39 import com.android.launcher3.Utilities;
40 import com.android.launcher3.anim.AnimatedFloat;
41 import com.android.launcher3.logging.InstanceId;
42 import com.android.launcher3.logging.InstanceIdSequence;
43 import com.android.launcher3.model.data.ItemInfo;
44 import com.android.launcher3.uioverrides.QuickstepLauncher;
45 import com.android.launcher3.util.DisplayController;
46 import com.android.launcher3.util.MultiPropertyFactory;
47 import com.android.launcher3.util.OnboardingPrefs;
48 import com.android.quickstep.RecentsAnimationCallbacks;
49 import com.android.quickstep.util.GroupTask;
50 import com.android.quickstep.views.RecentsView;
51 
52 import java.io.PrintWriter;
53 import java.util.Arrays;
54 
55 /**
56  * A data source which integrates with a Launcher instance
57  */
58 public class LauncherTaskbarUIController extends TaskbarUIController {
59 
60     private static final String TAG = "TaskbarUIController";
61 
62     public static final int MINUS_ONE_PAGE_PROGRESS_INDEX = 0;
63     public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
64     public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
65     public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
66 
67     public static final int DISPLAY_PROGRESS_COUNT = 4;
68 
69     private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
70             this::onInAppDisplayProgressChanged);
71     private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
72             new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
73                     AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
74 
75     private final QuickstepLauncher mLauncher;
76 
77     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
78             dp -> {
79                 onStashedInAppChanged(dp);
80                 if (mControllers != null && mControllers.taskbarViewController != null) {
81                     mControllers.taskbarViewController.onRotationChanged(dp);
82                 }
83             };
84 
85     // Initialized in init.
86     private final TaskbarLauncherStateController
87             mTaskbarLauncherStateController = new TaskbarLauncherStateController();
88 
LauncherTaskbarUIController(QuickstepLauncher launcher)89     public LauncherTaskbarUIController(QuickstepLauncher launcher) {
90         mLauncher = launcher;
91     }
92 
93     @Override
init(TaskbarControllers taskbarControllers)94     protected void init(TaskbarControllers taskbarControllers) {
95         super.init(taskbarControllers);
96 
97         mTaskbarLauncherStateController.init(mControllers, mLauncher,
98                 mControllers.getSharedState().sysuiStateFlags);
99 
100         mLauncher.setTaskbarUIController(this);
101 
102         onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
103 
104         onStashedInAppChanged(mLauncher.getDeviceProfile());
105         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
106 
107         // Restore the in-app display progress from before Taskbar was recreated.
108         float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues;
109         // Make a copy of the previous progress to set since updating the multiprop will update
110         // the property which also calls onInAppDisplayProgressChanged() which writes the current
111         // values into the shared state
112         prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length);
113         for (int i = 0; i < prevProgresses.length; i++) {
114             mTaskbarInAppDisplayProgressMultiProp.get(i).setValue(prevProgresses[i]);
115         }
116     }
117 
118     @Override
onDestroy()119     protected void onDestroy() {
120         super.onDestroy();
121         onLauncherResumedOrPaused(false);
122         mTaskbarLauncherStateController.onDestroy();
123 
124         mLauncher.setTaskbarUIController(null);
125         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
126         updateTaskTransitionSpec(true);
127     }
128 
onInAppDisplayProgressChanged()129     private void onInAppDisplayProgressChanged() {
130         if (mControllers != null) {
131             // Update our shared state so we can restore it if taskbar gets recreated.
132             for (int i = 0; i < DISPLAY_PROGRESS_COUNT; i++) {
133                 mControllers.getSharedState().inAppDisplayProgressMultiPropValues[i] =
134                         mTaskbarInAppDisplayProgressMultiProp.get(i).getValue();
135             }
136             // Ensure nav buttons react to our latest state if necessary.
137             mControllers.navbarButtonsViewController.updateNavButtonTranslationY();
138         }
139     }
140 
141     @Override
isTaskbarTouchable()142     protected boolean isTaskbarTouchable() {
143         return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
144                 && mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat());
145     }
146 
setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim)147     public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
148         mTaskbarLauncherStateController.setShouldDelayLauncherStateAnim(
149                 shouldDelayLauncherStateAnim);
150     }
151 
152     /**
153      * Adds the Launcher resume animator to the given animator set.
154      *
155      * This should be used to run a Launcher resume animation whose progress matches a
156      * swipe progress.
157      *
158      * @param placeholderDuration a placeholder duration to be used to ensure all full-length
159      *                            sub-animations are properly coordinated. This duration should not
160      *                            actually be used since this animation tracks a swipe progress.
161      */
addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration)162     protected void addLauncherResumeAnimation(AnimatorSet animation, int placeholderDuration) {
163         animation.play(onLauncherResumedOrPaused(
164                 /* isResumed= */ true,
165                 /* fromInit= */ false,
166                 /* startAnimation= */ false,
167                 placeholderDuration));
168     }
169 
170     /**
171      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
172      */
onLauncherResumedOrPaused(boolean isResumed)173     public void onLauncherResumedOrPaused(boolean isResumed) {
174         onLauncherResumedOrPaused(isResumed, false /* fromInit */);
175     }
176 
onLauncherResumedOrPaused(boolean isResumed, boolean fromInit)177     private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
178         onLauncherResumedOrPaused(
179                 isResumed,
180                 fromInit,
181                 /* startAnimation= */ true,
182                 DisplayController.isTransientTaskbar(mLauncher)
183                         ? TRANSIENT_TASKBAR_TRANSITION_DURATION
184                         : (!isResumed
185                                 ? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
186                                 : QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION));
187     }
188 
189     @Nullable
onLauncherResumedOrPaused( boolean isResumed, boolean fromInit, boolean startAnimation, int duration)190     private Animator onLauncherResumedOrPaused(
191             boolean isResumed, boolean fromInit, boolean startAnimation, int duration) {
192         // Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
193         // avoid updating taskbar state in that situation (when it's non-interactive -- or
194         // "background") to avoid premature animations.
195         if (ENABLE_SHELL_TRANSITIONS && isResumed
196                 && mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)
197                 && !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
198             return null;
199         }
200 
201         mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
202         return mTaskbarLauncherStateController.applyState(fromInit ? 0 : duration, startAnimation);
203     }
204 
refreshResumedState()205     public void refreshResumedState() {
206         onLauncherResumedOrPaused(mLauncher.hasBeenResumed());
207     }
208 
209     /**
210      * Create Taskbar animation when going from an app to Launcher as part of recents transition.
211      * @param toState If known, the state we will end up in when reaching Launcher.
212      * @param callbacks callbacks to track the recents animation lifecycle. The state change is
213      *                 automatically reset once the recents animation finishes
214      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)215     public Animator createAnimToLauncher(@NonNull LauncherState toState,
216             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
217         return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
218     }
219 
isDraggingItem()220     public boolean isDraggingItem() {
221         return mControllers.taskbarDragController.isDragging();
222     }
223 
224     @Override
onStashedInAppChanged()225     protected void onStashedInAppChanged() {
226         onStashedInAppChanged(mLauncher.getDeviceProfile());
227     }
228 
onStashedInAppChanged(DeviceProfile deviceProfile)229     private void onStashedInAppChanged(DeviceProfile deviceProfile) {
230         boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
231         deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
232         updateTaskTransitionSpec(taskbarStashedInApps);
233     }
234 
updateTaskTransitionSpec(boolean taskbarIsHidden)235     private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
236         try {
237             if (taskbarIsHidden) {
238                 // Clear custom task transition settings when the taskbar is stashed
239                 WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
240             } else {
241                 // Adjust task transition spec to account for taskbar being visible
242                 WindowManagerGlobal.getWindowManagerService().setTaskTransitionSpec(
243                         new TaskTransitionSpec(
244                                 mLauncher.getColor(R.color.taskbar_background)));
245             }
246         } catch (RemoteException e) {
247             // This shouldn't happen but if it does task animations won't look good until the
248             // taskbar stashing state is changed.
249             Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
250                     e);
251         }
252     }
253 
254     /**
255      * Starts a Taskbar EDU flow, if the user should see one upon launching an application.
256      */
showEduOnAppLaunch()257     public void showEduOnAppLaunch() {
258         if (!shouldShowEduOnAppLaunch()) {
259             return;
260         }
261 
262         // Persistent features EDU tooltip.
263         if (!DisplayController.isTransientTaskbar(mLauncher)) {
264             mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
265             return;
266         }
267 
268         // Transient swipe EDU tooltip.
269         mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
270     }
271 
272     /**
273      * Returns {@code true} if a Taskbar education should be shown on application launch.
274      */
shouldShowEduOnAppLaunch()275     public boolean shouldShowEduOnAppLaunch() {
276         if (Utilities.isRunningInTestHarness()) {
277             return false;
278         }
279 
280         // Persistent features EDU tooltip.
281         if (!DisplayController.isTransientTaskbar(mLauncher)) {
282             return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
283                     OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
284         }
285 
286         // Transient swipe EDU tooltip.
287         return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES;
288     }
289 
290     @Override
onTaskbarIconLaunched(ItemInfo item)291     public void onTaskbarIconLaunched(ItemInfo item) {
292         super.onTaskbarIconLaunched(item);
293         InstanceId instanceId = new InstanceIdSequence().newInstanceId();
294         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
295                 instanceId);
296     }
297 
298     /**
299      * Animates Taskbar elements during a transition to a Launcher state that should use in-app
300      * layouts.
301      *
302      * @param progress [0, 1]
303      *                 0 => use home layout
304      *                 1 => use in-app layout
305      */
onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex)306     public void onTaskbarInAppDisplayProgressUpdate(float progress, int progressIndex) {
307         mTaskbarInAppDisplayProgressMultiProp.get(progressIndex).setValue(progress);
308         if (mControllers == null) {
309             // This method can be called before init() is called.
310             return;
311         }
312         if (mControllers.uiController.isIconAlignedWithHotseat()
313                 && !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
314             // Only animate the nav buttons while home and not animating home, otherwise let
315             // the TaskbarViewController handle it.
316             mControllers.navbarButtonsViewController
317                     .getTaskbarNavButtonTranslationYForInAppDisplay()
318                     .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
319                             * mTaskbarInAppDisplayProgress.value);
320             mControllers.navbarButtonsViewController
321                     .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
322         }
323     }
324 
325     /** Returns true iff any in-app display progress > 0. */
shouldUseInAppLayout()326     public boolean shouldUseInAppLayout() {
327         return mTaskbarInAppDisplayProgress.value > 0;
328     }
329 
330     @Override
onExpandPip()331     public void onExpandPip() {
332         super.onExpandPip();
333         mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, false);
334         mTaskbarLauncherStateController.applyState();
335     }
336 
337     @Override
updateStateForSysuiFlags(int sysuiFlags)338     public void updateStateForSysuiFlags(int sysuiFlags) {
339         mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags);
340     }
341 
342     @Override
isIconAlignedWithHotseat()343     public boolean isIconAlignedWithHotseat() {
344         return mTaskbarLauncherStateController.isIconAlignedWithHotseat();
345     }
346 
347     @Override
isHotseatIconOnTopWhenAligned()348     public boolean isHotseatIconOnTopWhenAligned() {
349         return mTaskbarLauncherStateController.isInHotseatOnTopStates()
350                 && mTaskbarInAppDisplayProgressMultiProp.get(MINUS_ONE_PAGE_PROGRESS_INDEX)
351                     .getValue() == 0;
352     }
353 
354     @Override
isInOverview()355     protected boolean isInOverview() {
356         return mTaskbarLauncherStateController.isInOverview();
357     }
358 
359     @Override
getRecentsView()360     public RecentsView getRecentsView() {
361         return mLauncher.getOverviewPanel();
362     }
363 
364     @Override
launchSplitTasks(@onNull View taskView, @NonNull GroupTask groupTask)365     public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
366         mLauncher.launchSplitTasks(taskView, groupTask);
367     }
368 
369     @Override
onIconLayoutBoundsChanged()370     protected void onIconLayoutBoundsChanged() {
371         mTaskbarLauncherStateController.resetIconAlignment();
372     }
373 
374     @Override
dumpLogs(String prefix, PrintWriter pw)375     public void dumpLogs(String prefix, PrintWriter pw) {
376         super.dumpLogs(prefix, pw);
377 
378         pw.println(String.format("%s\tTaskbar in-app display progress: %.2f", prefix,
379                 mTaskbarInAppDisplayProgress.value));
380         mTaskbarInAppDisplayProgressMultiProp.dump(
381                 prefix + "\t\t",
382                 pw,
383                 "mTaskbarInAppDisplayProgressMultiProp",
384                 "MINUS_ONE_PAGE_PROGRESS_INDEX",
385                 "ALL_APPS_PAGE_PROGRESS_INDEX",
386                 "WIDGETS_PAGE_PROGRESS_INDEX",
387                 "SYSUI_SURFACE_PROGRESS_INDEX");
388 
389         mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
390     }
391 }
392