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.Display.DEFAULT_DISPLAY;
19 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
20 
21 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
22 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
23 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
24 
25 import android.content.ComponentCallbacks;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ActivityInfo;
29 import android.content.res.Configuration;
30 import android.hardware.display.DisplayManager;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.provider.Settings;
34 import android.view.Display;
35 
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 
39 import com.android.launcher3.BaseQuickstepLauncher;
40 import com.android.launcher3.DeviceProfile;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.config.FeatureFlags;
43 import com.android.launcher3.statemanager.StatefulActivity;
44 import com.android.launcher3.util.DisplayController;
45 import com.android.launcher3.util.DisplayController.Info;
46 import com.android.launcher3.util.SettingsCache;
47 import com.android.launcher3.util.SimpleBroadcastReceiver;
48 import com.android.quickstep.RecentsActivity;
49 import com.android.quickstep.SysUINavigationMode;
50 import com.android.quickstep.SysUINavigationMode.Mode;
51 import com.android.quickstep.SystemUiProxy;
52 import com.android.quickstep.TouchInteractionService;
53 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
54 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
55 
56 /**
57  * Class to manage taskbar lifecycle
58  */
59 public class TaskbarManager implements DisplayController.DisplayInfoChangeListener,
60         SysUINavigationMode.NavigationModeChangeListener {
61 
62     private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
63             Settings.Secure.USER_SETUP_COMPLETE);
64 
65     private final Context mContext;
66     private final DisplayController mDisplayController;
67     private final SysUINavigationMode mSysUINavigationMode;
68     private final TaskbarNavButtonController mNavButtonController;
69     private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
70     private final ComponentCallbacks mComponentCallbacks;
71     private final SimpleBroadcastReceiver mShutdownReceiver;
72 
73     // The source for this provider is set when Launcher is available
74     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
75             new ScopedUnfoldTransitionProgressProvider();
76 
77     private TaskbarActivityContext mTaskbarActivityContext;
78     private StatefulActivity mActivity;
79     /**
80      * Cache a copy here so we can initialize state whenever taskbar is recreated, since
81      * this class does not get re-initialized w/ new taskbars.
82      */
83     private final TaskbarSharedState mSharedState = new TaskbarSharedState();
84 
85     private static final int CHANGE_FLAGS =
86             CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS;
87 
88     private boolean mUserUnlocked = false;
89 
TaskbarManager(TouchInteractionService service)90     public TaskbarManager(TouchInteractionService service) {
91         mDisplayController = DisplayController.INSTANCE.get(service);
92         mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service);
93         Display display =
94                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
95         mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
96         mNavButtonController = new TaskbarNavButtonController(service,
97                 SystemUiProxy.INSTANCE.get(mContext), new Handler());
98         mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
99         mComponentCallbacks = new ComponentCallbacks() {
100             private Configuration mOldConfig = mContext.getResources().getConfiguration();
101 
102             @Override
103             public void onConfigurationChanged(Configuration newConfig) {
104                 int configDiff = mOldConfig.diff(newConfig);
105                 int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
106                         | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE;
107                 if ((configDiff & configsRequiringRecreate) != 0) {
108                     // Color has changed, recreate taskbar to reload background color & icons.
109                     recreateTaskbar();
110                 } else {
111                     // Config change might be handled without re-creating the taskbar
112                     if (mTaskbarActivityContext != null) {
113                         mTaskbarActivityContext.onConfigurationChanged(configDiff);
114                     }
115                 }
116                 mOldConfig = newConfig;
117             }
118 
119             @Override
120             public void onLowMemory() { }
121         };
122         mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
123 
124         mDisplayController.addChangeListener(this);
125         mSysUINavigationMode.addModeChangeListener(this);
126         SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
127                 mUserSetupCompleteListener);
128         mContext.registerComponentCallbacks(mComponentCallbacks);
129         mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
130 
131         recreateTaskbar();
132     }
133 
134     @Override
onNavigationModeChanged(Mode newMode)135     public void onNavigationModeChanged(Mode newMode) {
136         recreateTaskbar();
137     }
138 
139     @Override
onDisplayInfoChanged(Context context, Info info, int flags)140     public void onDisplayInfoChanged(Context context, Info info, int flags) {
141         if ((flags & CHANGE_FLAGS) != 0) {
142             recreateTaskbar();
143         }
144     }
145 
destroyExistingTaskbar()146     private void destroyExistingTaskbar() {
147         if (mTaskbarActivityContext != null) {
148             mTaskbarActivityContext.onDestroy();
149             mTaskbarActivityContext = null;
150         }
151     }
152 
153     /**
154      * Called when the user is unlocked
155      */
onUserUnlocked()156     public void onUserUnlocked() {
157         mUserUnlocked = true;
158         recreateTaskbar();
159     }
160 
161     /**
162      * Sets a {@link StatefulActivity} to act as taskbar callback
163      */
setActivity(@onNull StatefulActivity activity)164     public void setActivity(@NonNull StatefulActivity activity) {
165         mActivity = activity;
166         mUnfoldProgressProvider.setSourceProvider(getUnfoldTransitionProgressProviderForActivity(
167                 activity));
168 
169         if (mTaskbarActivityContext != null) {
170             mTaskbarActivityContext.setUIController(
171                     createTaskbarUIControllerForActivity(mActivity));
172         }
173     }
174 
175     /**
176      * Returns an {@link UnfoldTransitionProgressProvider} to use while the given StatefulActivity
177      * is active.
178      */
getUnfoldTransitionProgressProviderForActivity( StatefulActivity activity)179     private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
180             StatefulActivity activity) {
181         if (activity instanceof BaseQuickstepLauncher) {
182             return ((BaseQuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
183         }
184         return null;
185     }
186 
187     /**
188      * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
189      */
createTaskbarUIControllerForActivity(StatefulActivity activity)190     private TaskbarUIController createTaskbarUIControllerForActivity(StatefulActivity activity) {
191         if (activity instanceof BaseQuickstepLauncher) {
192             return new LauncherTaskbarUIController((BaseQuickstepLauncher) activity);
193         }
194         if (activity instanceof RecentsActivity) {
195             return new FallbackTaskbarUIController((RecentsActivity) activity);
196         }
197         return TaskbarUIController.DEFAULT;
198     }
199 
200     /**
201      * Clears a previously set {@link StatefulActivity}
202      */
clearActivity(@onNull StatefulActivity activity)203     public void clearActivity(@NonNull StatefulActivity activity) {
204         if (mActivity == activity) {
205             mActivity = null;
206             if (mTaskbarActivityContext != null) {
207                 mTaskbarActivityContext.setUIController(TaskbarUIController.DEFAULT);
208             }
209             mUnfoldProgressProvider.setSourceProvider(null);
210         }
211     }
212 
recreateTaskbar()213     private void recreateTaskbar() {
214         destroyExistingTaskbar();
215 
216         DeviceProfile dp =
217                 mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
218 
219         boolean isTaskBarEnabled =
220                 FeatureFlags.ENABLE_TASKBAR.get() && dp != null && dp.isTaskbarPresent;
221 
222         if (!isTaskBarEnabled) {
223             SystemUiProxy.INSTANCE.get(mContext)
224                     .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
225             return;
226         }
227 
228         mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp.copy(mContext),
229                 mNavButtonController, mUnfoldProgressProvider);
230 
231         mTaskbarActivityContext.init(mSharedState);
232         if (mActivity != null) {
233             mTaskbarActivityContext.setUIController(
234                     createTaskbarUIControllerForActivity(mActivity));
235         }
236     }
237 
onSystemUiFlagsChanged(int systemUiStateFlags)238     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
239         mSharedState.sysuiStateFlags = systemUiStateFlags;
240         if (mTaskbarActivityContext != null) {
241             mTaskbarActivityContext.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
242         }
243     }
244 
245     /**
246      * Sets the flag indicating setup UI is visible
247      */
setSetupUIVisible(boolean isVisible)248     public void setSetupUIVisible(boolean isVisible) {
249         mSharedState.setupUIVisible = isVisible;
250         if (mTaskbarActivityContext != null) {
251             mTaskbarActivityContext.setSetupUIVisible(isVisible);
252         }
253     }
254 
onRotationProposal(int rotation, boolean isValid)255     public void onRotationProposal(int rotation, boolean isValid) {
256         if (mTaskbarActivityContext != null) {
257             mTaskbarActivityContext.onRotationProposal(rotation, isValid);
258         }
259     }
260 
disableNavBarElements(int displayId, int state1, int state2, boolean animate)261     public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) {
262         if (mTaskbarActivityContext != null) {
263             mTaskbarActivityContext.disableNavBarElements(displayId, state1, state2, animate);
264         }
265     }
266 
onSystemBarAttributesChanged(int displayId, int behavior)267     public void onSystemBarAttributesChanged(int displayId, int behavior) {
268         if (mTaskbarActivityContext != null) {
269             mTaskbarActivityContext.onSystemBarAttributesChanged(displayId, behavior);
270         }
271     }
272 
onNavButtonsDarkIntensityChanged(float darkIntensity)273     public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
274         if (mTaskbarActivityContext != null) {
275             mTaskbarActivityContext.onNavButtonsDarkIntensityChanged(darkIntensity);
276         }
277     }
278 
279     /**
280      * Called when the manager is no longer needed
281      */
destroy()282     public void destroy() {
283         destroyExistingTaskbar();
284         mDisplayController.removeChangeListener(this);
285         mSysUINavigationMode.removeModeChangeListener(this);
286         SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
287                 mUserSetupCompleteListener);
288         mContext.unregisterComponentCallbacks(mComponentCallbacks);
289         mContext.unregisterReceiver(mShutdownReceiver);
290     }
291 
getCurrentActivityContext()292     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
293         return mTaskbarActivityContext;
294     }
295 }
296