1 /* 2 * Copyright (C) 2022 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 17 package com.android.wm.shell.shared.desktopmode; 18 19 import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED; 20 import static android.window.DesktopExperienceFlags.ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE; 21 22 import static com.android.server.display.feature.flags.Flags.enableDisplayContentModeManagement; 23 import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.app.TaskInfo; 28 import android.content.Context; 29 import android.hardware.display.DisplayManager; 30 import android.os.SystemProperties; 31 import android.view.Display; 32 import android.view.WindowManager; 33 import android.window.DesktopExperienceFlags; 34 import android.window.DesktopModeFlags; 35 36 import com.android.internal.R; 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.window.flags.Flags; 39 40 import java.io.PrintWriter; 41 import java.util.Arrays; 42 43 /** 44 * Constants for desktop mode feature 45 */ 46 // TODO(b/237575897): Move this file to the `com.android.wm.shell.shared.desktopmode` package 47 public class DesktopModeStatus { 48 49 private static final String TAG = "DesktopModeStatus"; 50 51 @Nullable 52 private static Boolean sIsLargeScreenDevice = null; 53 54 /** 55 * Flag to indicate whether task resizing is veiled. 56 */ 57 private static final boolean IS_VEILED_RESIZE_ENABLED = SystemProperties.getBoolean( 58 "persist.wm.debug.desktop_veiled_resizing", true); 59 60 /** 61 * Flag to indicate is moving task to another display is enabled. 62 */ 63 public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( 64 "persist.wm.debug.desktop_change_display", false); 65 66 /** 67 * Flag to indicate whether to apply shadows to windows in desktop mode. 68 */ 69 private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean( 70 "persist.wm.debug.desktop_use_window_shadows", true); 71 72 /** 73 * Flag to indicate whether to apply shadows to the focused window in desktop mode. 74 * 75 * Note: this flag is only relevant if USE_WINDOW_SHADOWS is false. 76 */ 77 private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean( 78 "persist.wm.debug.desktop_use_window_shadows_focused_window", false); 79 80 /** 81 * Flag to indicate whether to use rounded corners for windows in desktop mode. 82 */ 83 private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean( 84 "persist.wm.debug.desktop_use_rounded_corners", true); 85 86 /** 87 * Flag to indicate whether to restrict desktop mode to supported devices. 88 */ 89 private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean( 90 "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); 91 92 private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS = 93 SystemProperties.getBoolean( 94 "persist.wm.debug.use_app_to_web_build_time_generic_links", true); 95 96 /** Whether the desktop density override is enabled. */ 97 public static final boolean DESKTOP_DENSITY_OVERRIDE_ENABLED = 98 SystemProperties.getBoolean("persist.wm.debug.desktop_mode_density_enabled", false); 99 100 /** Override density for tasks when they're inside the desktop. */ 101 public static final int DESKTOP_DENSITY_OVERRIDE = 102 SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284); 103 104 /** The minimum override density allowed for tasks inside the desktop. */ 105 private static final int DESKTOP_DENSITY_MIN = 100; 106 107 /** The maximum override density allowed for tasks inside the desktop. */ 108 private static final int DESKTOP_DENSITY_MAX = 1000; 109 110 /** The number of [WindowDecorViewHost] instances to warm up on system start. */ 111 private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2; 112 113 /** 114 * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the 115 * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. 116 * 117 * <p>If it is not defined, then {@code R.integer.config_enterDesktopByDefaultOnFreeformDisplay} 118 * is used. 119 */ 120 public static final String ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP = 121 "persist.wm.debug.enter_desktop_by_default_on_freeform_display"; 122 123 /** 124 * Sysprop declaring whether to enable drag-to-maximize for desktop windows. 125 * 126 * <p>If it is not defined, then {@code R.integer.config_dragToMaximizeInDesktopMode} 127 * is used. 128 */ 129 public static final String ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP = 130 "persist.wm.debug.enable_drag_to_maximize"; 131 132 /** 133 * Sysprop declaring the maximum number of Tasks to show in Desktop Mode at any one time. 134 * 135 * <p>If it is not defined, then {@code R.integer.config_maxDesktopWindowingActiveTasks} is 136 * used. 137 * 138 * <p>The limit does NOT affect Picture-in-Picture, Bubbles, or System Modals (like a screen 139 * recording window, or Bluetooth pairing window). 140 */ 141 private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit"; 142 143 /** 144 * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start. 145 * 146 * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used. 147 */ 148 private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP = 149 "persist.wm.debug.desktop_window_decor_pre_warm_size"; 150 151 /** 152 * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. 153 */ isVeiledResizeEnabled()154 public static boolean isVeiledResizeEnabled() { 155 return IS_VEILED_RESIZE_ENABLED; 156 } 157 158 /** 159 * Return whether to use window shadows. 160 * 161 * @param isFocusedWindow whether the window to apply shadows to is focused 162 */ useWindowShadow(boolean isFocusedWindow)163 public static boolean useWindowShadow(boolean isFocusedWindow) { 164 return USE_WINDOW_SHADOWS 165 || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow); 166 } 167 168 /** 169 * Return whether to use rounded corners for windows. 170 */ useRoundedCorners()171 public static boolean useRoundedCorners() { 172 return USE_ROUNDED_CORNERS; 173 } 174 175 /** 176 * Return {@code true} if desktop mode should be restricted to supported devices. 177 */ 178 @VisibleForTesting enforceDeviceRestrictions()179 public static boolean enforceDeviceRestrictions() { 180 return ENFORCE_DEVICE_RESTRICTIONS; 181 } 182 183 /** 184 * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time. 185 */ getMaxTaskLimit(@onNull Context context)186 public static int getMaxTaskLimit(@NonNull Context context) { 187 return SystemProperties.getInt(MAX_TASK_LIMIT_SYS_PROP, 188 context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks)); 189 } 190 191 /** 192 * Return the maximum size of the window decoration surface control view host pool, or zero if 193 * there should be no pooling. 194 */ getWindowDecorScvhPoolSize(@onNull Context context)195 public static int getWindowDecorScvhPoolSize(@NonNull Context context) { 196 if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_SCVH_CACHE.isTrue()) return 0; 197 final int maxTaskLimit = getMaxTaskLimit(context); 198 if (maxTaskLimit > 0) { 199 return maxTaskLimit; 200 } 201 // TODO: b/368032552 - task limit equal to 0 means unlimited. Figure out what the pool 202 // size should be in that case. 203 return 0; 204 } 205 206 /** The number of [WindowDecorViewHost] instances to warm up on system start. */ getWindowDecorPreWarmSize()207 public static int getWindowDecorPreWarmSize() { 208 return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP, 209 WINDOW_DECOR_PRE_WARM_SIZE); 210 } 211 212 /** 213 * Return {@code true} if the current device supports desktop mode. 214 */ isDesktopModeSupported(@onNull Context context)215 private static boolean isDesktopModeSupported(@NonNull Context context) { 216 return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported); 217 } 218 219 /** 220 * Return {@code true} if the current device supports the developer option for desktop mode. 221 */ isDesktopModeDevOptionSupported(@onNull Context context)222 private static boolean isDesktopModeDevOptionSupported(@NonNull Context context) { 223 return context.getResources().getBoolean(R.bool.config_isDesktopModeDevOptionSupported); 224 } 225 226 /** 227 * Return {@code true} if the current device can host desktop sessions on its internal display. 228 */ canInternalDisplayHostDesktops(@onNull Context context)229 private static boolean canInternalDisplayHostDesktops(@NonNull Context context) { 230 return context.getResources().getBoolean(R.bool.config_canInternalDisplayHostDesktops); 231 } 232 233 234 /** 235 * Return {@code true} if desktop mode dev option should be shown on current device 236 */ canShowDesktopModeDevOption(@onNull Context context)237 public static boolean canShowDesktopModeDevOption(@NonNull Context context) { 238 return isDeviceEligibleForDesktopModeDevOption(context) 239 && Flags.showDesktopWindowingDevOption(); 240 } 241 242 /** 243 * Return {@code true} if desktop mode dev option should be shown on current device 244 */ canShowDesktopExperienceDevOption(@onNull Context context)245 public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) { 246 return Flags.showDesktopExperienceDevOption() 247 && isDeviceEligibleForDesktopMode(context); 248 } 249 250 /** Returns if desktop mode dev option should be enabled if there is no user override. */ shouldDevOptionBeEnabledByDefault(Context context)251 public static boolean shouldDevOptionBeEnabledByDefault(Context context) { 252 return isDeviceEligibleForDesktopMode(context) 253 && Flags.enableDesktopWindowingMode(); 254 } 255 256 /** 257 * Return {@code true} if desktop mode is enabled and can be entered on the current device. 258 */ canEnterDesktopMode(@onNull Context context)259 public static boolean canEnterDesktopMode(@NonNull Context context) { 260 return (isDeviceEligibleForDesktopMode(context) 261 && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODE.isTrue()) 262 || isDesktopModeEnabledByDevOption(context); 263 } 264 265 /** 266 * Check if Desktop mode should be enabled because the dev option is shown and enabled. 267 */ isDesktopModeEnabledByDevOption(@onNull Context context)268 private static boolean isDesktopModeEnabledByDevOption(@NonNull Context context) { 269 return DesktopModeFlags.isDesktopModeForcedEnabled() 270 && canShowDesktopModeDevOption(context); 271 } 272 273 /** 274 * Check to see if a display should have desktop mode enabled or not. Internal 275 * and external displays have separate logic. 276 */ isDesktopModeSupportedOnDisplay(Context context, Display display)277 public static boolean isDesktopModeSupportedOnDisplay(Context context, Display display) { 278 if (!canEnterDesktopMode(context)) { 279 return false; 280 } 281 if (!enforceDeviceRestrictions()) { 282 return true; 283 } 284 if (display.getType() == Display.TYPE_INTERNAL) { 285 return canInternalDisplayHostDesktops(context); 286 } 287 288 // TODO (b/395014779): Change this to use WM API 289 if ((display.getType() == Display.TYPE_EXTERNAL 290 || display.getType() == Display.TYPE_OVERLAY) 291 && enableDisplayContentModeManagement()) { 292 final WindowManager wm = context.getSystemService(WindowManager.class); 293 return wm != null && wm.shouldShowSystemDecors(display.getDisplayId()); 294 } 295 296 return false; 297 } 298 299 /** 300 * Returns whether the multiple desktops feature is enabled for this device (both backend and 301 * frontend implementations). 302 */ enableMultipleDesktops(@onNull Context context)303 public static boolean enableMultipleDesktops(@NonNull Context context) { 304 return DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue() 305 && Flags.enableMultipleDesktopsFrontend() 306 && canEnterDesktopMode(context); 307 } 308 309 /** 310 * @return {@code true} if this device is requesting to show the app handle despite non 311 * necessarily enabling desktop mode 312 */ overridesShowAppHandle(@onNull Context context)313 public static boolean overridesShowAppHandle(@NonNull Context context) { 314 return (Flags.showAppHandleLargeScreens() || enableBubbleToFullscreen()) 315 && deviceHasLargeScreen(context); 316 } 317 318 /** 319 * @return If {@code true} we set opaque background for all freeform tasks to prevent freeform 320 * tasks below from being visible if freeform task window above is translucent. 321 * Otherwise if fluid resize is enabled, add a background to freeform tasks. 322 */ shouldSetBackground(@onNull TaskInfo taskInfo)323 public static boolean shouldSetBackground(@NonNull TaskInfo taskInfo) { 324 return taskInfo.isFreeform() && (!DesktopModeStatus.isVeiledResizeEnabled() 325 || DesktopModeFlags.ENABLE_OPAQUE_BACKGROUND_FOR_TRANSPARENT_WINDOWS.isTrue()); 326 } 327 328 /** 329 * @return {@code true} if the app handle should be shown because desktop mode is enabled or 330 * the device has a large screen 331 */ canEnterDesktopModeOrShowAppHandle(@onNull Context context)332 public static boolean canEnterDesktopModeOrShowAppHandle(@NonNull Context context) { 333 return canEnterDesktopMode(context) || overridesShowAppHandle(context); 334 } 335 336 /** 337 * Return {@code true} if the override desktop density is enabled and valid. 338 */ useDesktopOverrideDensity()339 public static boolean useDesktopOverrideDensity() { 340 return isDesktopDensityOverrideEnabled() && isValidDesktopDensityOverrideSet(); 341 } 342 343 /** 344 * Returns {@code true} if the app-to-web feature is using the build-time generic links list. 345 */ useAppToWebBuildTimeGenericLinks()346 public static boolean useAppToWebBuildTimeGenericLinks() { 347 return USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS; 348 } 349 350 /** 351 * Return {@code true} if the override desktop density is enabled. 352 */ isDesktopDensityOverrideEnabled()353 private static boolean isDesktopDensityOverrideEnabled() { 354 return DESKTOP_DENSITY_OVERRIDE_ENABLED; 355 } 356 357 /** 358 * Return {@code true} if the override desktop density is set and within a valid range. 359 */ isValidDesktopDensityOverrideSet()360 private static boolean isValidDesktopDensityOverrideSet() { 361 return DESKTOP_DENSITY_OVERRIDE >= DESKTOP_DENSITY_MIN 362 && DESKTOP_DENSITY_OVERRIDE <= DESKTOP_DENSITY_MAX; 363 } 364 365 /** 366 * Return {@code true} if desktop mode is unrestricted and is supported on the device. 367 */ isDeviceEligibleForDesktopMode(@onNull Context context)368 public static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) { 369 if (!enforceDeviceRestrictions()) { 370 return true; 371 } 372 // If projected display is enabled, #canInternalDisplayHostDesktops is no longer a 373 // requirement. 374 final boolean desktopModeSupported = ENABLE_PROJECTED_DISPLAY_DESKTOP_MODE.isTrue() 375 ? isDesktopModeSupported(context) : (isDesktopModeSupported(context) 376 && canInternalDisplayHostDesktops(context)); 377 final boolean desktopModeSupportedByDevOptions = 378 Flags.enableDesktopModeThroughDevOption() 379 && isDesktopModeDevOptionSupported(context); 380 return desktopModeSupported || desktopModeSupportedByDevOptions; 381 } 382 383 /** 384 * Return {@code true} if the developer option for desktop mode is unrestricted and is supported 385 * in the device. 386 * 387 * Note that, if {@link #isDeviceEligibleForDesktopMode(Context)} is true, then 388 * {@link #isDeviceEligibleForDesktopModeDevOption(Context)} is also true. 389 */ isDeviceEligibleForDesktopModeDevOption(@onNull Context context)390 private static boolean isDeviceEligibleForDesktopModeDevOption(@NonNull Context context) { 391 if (!enforceDeviceRestrictions()) { 392 return true; 393 } 394 final boolean desktopModeSupported = isDesktopModeSupported(context) 395 && canInternalDisplayHostDesktops(context); 396 return desktopModeSupported || isDesktopModeDevOptionSupported(context); 397 } 398 399 /** 400 * @return {@code true} if this device has an internal large screen 401 */ deviceHasLargeScreen(@onNull Context context)402 private static boolean deviceHasLargeScreen(@NonNull Context context) { 403 if (sIsLargeScreenDevice == null) { 404 sIsLargeScreenDevice = Arrays.stream( 405 context.getSystemService(DisplayManager.class) 406 .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)) 407 .filter(display -> display.getType() == Display.TYPE_INTERNAL) 408 .anyMatch(display -> display.getMinSizeDimensionDp() 409 >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP); 410 } 411 return sIsLargeScreenDevice; 412 } 413 414 /** 415 * Return {@code true} if a display should enter desktop mode by default when the windowing mode 416 * of the display's root [TaskDisplayArea] is set to WINDOWING_MODE_FREEFORM. 417 */ enterDesktopByDefaultOnFreeformDisplay(@onNull Context context)418 public static boolean enterDesktopByDefaultOnFreeformDisplay(@NonNull Context context) { 419 if (!DesktopExperienceFlags.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS.isTrue()) { 420 return false; 421 } 422 return SystemProperties.getBoolean(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, 423 context.getResources().getBoolean( 424 R.bool.config_enterDesktopByDefaultOnFreeformDisplay)); 425 } 426 427 /** 428 * Return {@code true} if a window should be maximized when it's dragged to the top edge of the 429 * screen. 430 */ shouldMaximizeWhenDragToTopEdge(@onNull Context context)431 public static boolean shouldMaximizeWhenDragToTopEdge(@NonNull Context context) { 432 if (!DesktopExperienceFlags.ENABLE_DRAG_TO_MAXIMIZE.isTrue()) { 433 return false; 434 } 435 return SystemProperties.getBoolean(ENABLE_DRAG_TO_MAXIMIZE_SYS_PROP, 436 context.getResources().getBoolean(R.bool.config_dragToMaximizeInDesktopMode)); 437 } 438 439 /** Dumps DesktopModeStatus flags and configs. */ dump(PrintWriter pw, String prefix, Context context)440 public static void dump(PrintWriter pw, String prefix, Context context) { 441 String innerPrefix = prefix + " "; 442 pw.print(prefix); pw.println(TAG); 443 pw.print(innerPrefix); pw.print("maxTaskLimit="); pw.println(getMaxTaskLimit(context)); 444 445 pw.print(innerPrefix); pw.print("maxTaskLimit config override="); 446 pw.println(context.getResources().getInteger( 447 R.integer.config_maxDesktopWindowingActiveTasks)); 448 449 SystemProperties.Handle maxTaskLimitHandle = SystemProperties.find(MAX_TASK_LIMIT_SYS_PROP); 450 pw.print(innerPrefix); pw.print("maxTaskLimit sysprop="); 451 pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1)); 452 453 pw.print(innerPrefix); pw.print("showAppHandle config override="); 454 pw.println(overridesShowAppHandle(context)); 455 } 456 } 457