• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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