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.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
19 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
20 
21 import android.animation.Animator;
22 import android.annotation.ColorInt;
23 import android.os.RemoteException;
24 import android.util.Log;
25 import android.view.MotionEvent;
26 import android.view.TaskTransitionSpec;
27 import android.view.WindowManagerGlobal;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.launcher3.BaseQuickstepLauncher;
32 import com.android.launcher3.DeviceProfile;
33 import com.android.launcher3.LauncherState;
34 import com.android.launcher3.QuickstepTransitionManager;
35 import com.android.launcher3.R;
36 import com.android.launcher3.Utilities;
37 import com.android.launcher3.config.FeatureFlags;
38 import com.android.launcher3.logging.InstanceId;
39 import com.android.launcher3.logging.InstanceIdSequence;
40 import com.android.launcher3.model.data.ItemInfoWithIcon;
41 import com.android.launcher3.model.data.WorkspaceItemInfo;
42 import com.android.launcher3.util.OnboardingPrefs;
43 import com.android.quickstep.AnimatedFloat;
44 import com.android.quickstep.RecentsAnimationCallbacks;
45 
46 import java.util.Arrays;
47 import java.util.Set;
48 import java.util.stream.Stream;
49 
50 /**
51  * A data source which integrates with a Launcher instance
52  */
53 public class LauncherTaskbarUIController extends TaskbarUIController {
54 
55     private static final String TAG = "TaskbarUIController";
56 
57     private final BaseQuickstepLauncher mLauncher;
58 
59     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
60             this::onStashedInAppChanged;
61 
62     // Initialized in init.
63     private AnimatedFloat mTaskbarOverrideBackgroundAlpha;
64     private TaskbarKeyguardController mKeyguardController;
65     private final TaskbarLauncherStateController
66             mTaskbarLauncherStateController = new TaskbarLauncherStateController();
67 
68     private final DeviceProfile.OnDeviceProfileChangeListener mProfileChangeListener =
69             new DeviceProfile.OnDeviceProfileChangeListener() {
70                 @Override
71                 public void onDeviceProfileChanged(DeviceProfile dp) {
72                     mControllers.taskbarViewController.onRotationChanged(
73                             mLauncher.getDeviceProfile());
74                 }
75             };
76 
LauncherTaskbarUIController(BaseQuickstepLauncher launcher)77     public LauncherTaskbarUIController(BaseQuickstepLauncher launcher) {
78         mLauncher = launcher;
79     }
80 
81     @Override
init(TaskbarControllers taskbarControllers)82     protected void init(TaskbarControllers taskbarControllers) {
83         super.init(taskbarControllers);
84 
85         mTaskbarLauncherStateController.init(mControllers, mLauncher);
86         mTaskbarOverrideBackgroundAlpha = mControllers.taskbarDragLayerController
87                 .getOverrideBackgroundAlpha();
88 
89         mLauncher.setTaskbarUIController(this);
90         mKeyguardController = taskbarControllers.taskbarKeyguardController;
91 
92         onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
93 
94         onStashedInAppChanged(mLauncher.getDeviceProfile());
95         mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
96         mLauncher.addOnDeviceProfileChangeListener(mProfileChangeListener);
97     }
98 
99     @Override
onDestroy()100     protected void onDestroy() {
101         super.onDestroy();
102         onLauncherResumedOrPaused(false);
103         mTaskbarLauncherStateController.onDestroy();
104 
105         mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
106         mLauncher.setTaskbarUIController(null);
107         mLauncher.removeOnDeviceProfileChangeListener(mProfileChangeListener);
108         updateTaskTransitionSpec(true);
109     }
110 
111     @Override
isTaskbarTouchable()112     protected boolean isTaskbarTouchable() {
113         return !mTaskbarLauncherStateController.isAnimatingToLauncher();
114     }
115 
116     /**
117      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
118      */
onLauncherResumedOrPaused(boolean isResumed)119     public void onLauncherResumedOrPaused(boolean isResumed) {
120         onLauncherResumedOrPaused(isResumed, false /* fromInit */);
121     }
122 
onLauncherResumedOrPaused(boolean isResumed, boolean fromInit)123     private void onLauncherResumedOrPaused(boolean isResumed, boolean fromInit) {
124         if (mKeyguardController.isScreenOff()) {
125             if (!isResumed) {
126                 return;
127             } else {
128                 // Resuming implicitly means device unlocked
129                 mKeyguardController.setScreenOn();
130             }
131         }
132 
133         mTaskbarLauncherStateController.updateStateForFlag(FLAG_RESUMED, isResumed);
134         mTaskbarLauncherStateController.applyState(
135                 fromInit ? 0 : QuickstepTransitionManager.CONTENT_ALPHA_DURATION);
136     }
137 
138     /**
139      * Create Taskbar animation when going from an app to Launcher as part of recents transition.
140      * @param toState If known, the state we will end up in when reaching Launcher.
141      * @param callbacks callbacks to track the recents animation lifecycle. The state change is
142      *                 automatically reset once the recents animation finishes
143      */
createAnimToLauncher(@onNull LauncherState toState, @NonNull RecentsAnimationCallbacks callbacks, long duration)144     public Animator createAnimToLauncher(@NonNull LauncherState toState,
145             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
146         return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
147     }
148 
149     /**
150      * @param ev MotionEvent in screen coordinates.
151      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
152      */
isEventOverAnyTaskbarItem(MotionEvent ev)153     public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
154         return mControllers.taskbarViewController.isEventOverAnyItem(ev)
155                 || mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
156     }
157 
isDraggingItem()158     public boolean isDraggingItem() {
159         return mControllers.taskbarDragController.isDragging();
160     }
161 
162     @Override
onStashedInAppChanged()163     protected void onStashedInAppChanged() {
164         onStashedInAppChanged(mLauncher.getDeviceProfile());
165     }
166 
onStashedInAppChanged(DeviceProfile deviceProfile)167     private void onStashedInAppChanged(DeviceProfile deviceProfile) {
168         boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
169         deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
170         updateTaskTransitionSpec(taskbarStashedInApps);
171     }
172 
updateTaskTransitionSpec(boolean taskbarIsHidden)173     private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
174         try {
175             if (taskbarIsHidden) {
176                 // Clear custom task transition settings when the taskbar is stashed
177                 WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
178             } else {
179                 // Adjust task transition spec to account for taskbar being visible
180                 @ColorInt int taskAnimationBackgroundColor =
181                         mLauncher.getColor(R.color.taskbar_background);
182 
183                 TaskTransitionSpec customTaskAnimationSpec = new TaskTransitionSpec(
184                         taskAnimationBackgroundColor,
185                         Set.of(ITYPE_EXTRA_NAVIGATION_BAR)
186                 );
187                 WindowManagerGlobal.getWindowManagerService()
188                         .setTaskTransitionSpec(customTaskAnimationSpec);
189             }
190         } catch (RemoteException e) {
191             // This shouldn't happen but if it does task animations won't look good until the
192             // taskbar stashing state is changed.
193             Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
194                     e);
195         }
196     }
197 
198     /**
199      * Sets whether the background behind the taskbar/nav bar should be hidden.
200      */
forceHideBackground(boolean forceHide)201     public void forceHideBackground(boolean forceHide) {
202         mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
203     }
204 
205     @Override
getAppIconsForEdu()206     public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
207         return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
208     }
209 
210     /**
211      * Starts the taskbar education flow, if the user hasn't seen it yet.
212      */
showEdu()213     public void showEdu() {
214         if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()
215                 || Utilities.IS_RUNNING_IN_TEST_HARNESS
216                 || mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN)) {
217             return;
218         }
219         mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
220 
221         mControllers.taskbarEduController.showEdu();
222     }
223 
224     /**
225      * Manually ends the taskbar education flow.
226      */
hideEdu()227     public void hideEdu() {
228         if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
229             return;
230         }
231 
232         mControllers.taskbarEduController.hideEdu();
233     }
234 
235     @Override
onTaskbarIconLaunched(WorkspaceItemInfo item)236     public void onTaskbarIconLaunched(WorkspaceItemInfo item) {
237         InstanceId instanceId = new InstanceIdSequence().newInstanceId();
238         mLauncher.logAppLaunch(mControllers.taskbarActivityContext.getStatsLogManager(), item,
239                 instanceId);
240     }
241 
242     @Override
setSystemGestureInProgress(boolean inProgress)243     public void setSystemGestureInProgress(boolean inProgress) {
244         super.setSystemGestureInProgress(inProgress);
245         // Launcher's ScrimView will draw the background throughout the gesture. But once the
246         // gesture ends, start drawing taskbar's background again since launcher might stop drawing.
247         forceHideBackground(inProgress);
248     }
249 }
250