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