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