• 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 package com.android.launcher3.util.window;
17 
18 import static android.view.Display.DEFAULT_DISPLAY;
19 
20 import static com.android.launcher3.Utilities.dpToPx;
21 import static com.android.launcher3.Utilities.dpiFromPx;
22 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
23 import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT;
24 import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_HEIGHT_LANDSCAPE;
25 import static com.android.launcher3.testing.shared.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
26 import static com.android.launcher3.testing.shared.ResourceUtils.NAV_BAR_INTERACTION_MODE_RES_NAME;
27 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT;
28 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
29 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
30 import static com.android.launcher3.util.RotationUtils.deltaRotation;
31 import static com.android.launcher3.util.RotationUtils.rotateRect;
32 import static com.android.launcher3.util.RotationUtils.rotateSize;
33 
34 import android.content.Context;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.graphics.Insets;
38 import android.graphics.Point;
39 import android.graphics.Rect;
40 import android.hardware.display.DisplayManager;
41 import android.util.ArrayMap;
42 import android.util.Log;
43 import android.view.Display;
44 import android.view.DisplayCutout;
45 import android.view.Surface;
46 import android.view.WindowInsets;
47 import android.view.WindowManager;
48 import android.view.WindowMetrics;
49 
50 import androidx.annotation.NonNull;
51 import androidx.annotation.VisibleForTesting;
52 
53 import com.android.launcher3.R;
54 import com.android.launcher3.dagger.LauncherAppSingleton;
55 import com.android.launcher3.dagger.LauncherBaseAppComponent;
56 import com.android.launcher3.testing.shared.ResourceUtils;
57 import com.android.launcher3.util.DaggerSingletonObject;
58 import com.android.launcher3.util.NavigationMode;
59 import com.android.launcher3.util.WindowBounds;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 
64 import javax.inject.Inject;
65 
66 /**
67  * Utility class for mocking some window manager behaviours
68  */
69 @LauncherAppSingleton
70 public class WindowManagerProxy {
71 
72     private static final String TAG = "WindowManagerProxy";
73     public static final int MIN_TABLET_WIDTH = 600;
74 
75     public static final DaggerSingletonObject<WindowManagerProxy> INSTANCE =
76             new DaggerSingletonObject<>(LauncherBaseAppComponent::getWmProxy);
77 
78     protected final boolean mTaskbarDrawnInProcess;
79 
80     @Inject
WindowManagerProxy()81     public WindowManagerProxy() {
82         this(false);
83     }
84 
WindowManagerProxy(boolean taskbarDrawnInProcess)85     protected WindowManagerProxy(boolean taskbarDrawnInProcess) {
86         mTaskbarDrawnInProcess = taskbarDrawnInProcess;
87     }
88 
89     /**
90      * Returns true if taskbar is drawn in process
91      */
isTaskbarDrawnInProcess()92     public boolean isTaskbarDrawnInProcess() {
93         return mTaskbarDrawnInProcess;
94     }
95 
96     /**
97      * Returns a map of normalized info of internal displays to estimated window bounds
98      * for that display
99      */
estimateInternalDisplayBounds( Context displayInfoContext)100     public ArrayMap<CachedDisplayInfo, List<WindowBounds>> estimateInternalDisplayBounds(
101             Context displayInfoContext) {
102         CachedDisplayInfo info = getDisplayInfo(displayInfoContext).normalize(this);
103         List<WindowBounds> bounds = estimateWindowBounds(displayInfoContext, info);
104         ArrayMap<CachedDisplayInfo, List<WindowBounds>> result = new ArrayMap<>();
105         result.put(info, bounds);
106         return result;
107     }
108 
109     /**
110      * Returns if we are in desktop mode or not.
111      */
isInDesktopMode(int displayId)112     public boolean isInDesktopMode(int displayId) {
113         return false;
114     }
115 
116     /**
117      * Returns if the pinned taskbar should be shown when home is visible.
118      */
showLockedTaskbarOnHome(Context displayInfoContext)119     public boolean showLockedTaskbarOnHome(Context displayInfoContext) {
120         return false;
121     }
122 
123     /**
124      * Returns whether the display is a freeform display for which taskbar should be pinned
125      * and showing desktop tasks.
126      */
showDesktopTaskbarForFreeformDisplay(Context displayInfoContext)127     public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) {
128         return false;
129     }
130 
131     /**
132      * Returns if the home is visible.
133      */
isHomeVisible(Context context)134     public boolean isHomeVisible(Context context) {
135         return false;
136     }
137 
138     /**
139      * Returns the real bounds for the provided display after applying any insets normalization
140      */
getRealBounds(Context displayInfoContext, CachedDisplayInfo info)141     public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
142         WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
143                 .getMaximumWindowMetrics();
144         Rect insets = new Rect();
145         normalizeWindowInsets(displayInfoContext, windowMetrics.getWindowInsets(), insets);
146         return new WindowBounds(windowMetrics.getBounds(), insets, info.rotation);
147     }
148 
149     /**
150      * Returns an updated insets, accounting for various Launcher UI specific overrides like taskbar
151      */
normalizeWindowInsets(Context context, WindowInsets oldInsets, Rect outInsets)152     public WindowInsets normalizeWindowInsets(Context context, WindowInsets oldInsets,
153             Rect outInsets) {
154         if (!mTaskbarDrawnInProcess) {
155             outInsets.set(oldInsets.getSystemWindowInsetLeft(), oldInsets.getSystemWindowInsetTop(),
156                     oldInsets.getSystemWindowInsetRight(), oldInsets.getSystemWindowInsetBottom());
157             return oldInsets;
158         }
159 
160         WindowInsets.Builder insetsBuilder = new WindowInsets.Builder(oldInsets);
161         Insets navInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
162 
163         Resources systemRes = context.getResources();
164         Configuration config = systemRes.getConfiguration();
165 
166         boolean isLargeScreen = config.smallestScreenWidthDp > MIN_TABLET_WIDTH;
167         boolean isGesture = isGestureNav(context);
168         boolean isPortrait = config.screenHeightDp > config.screenWidthDp;
169 
170         int bottomNav = isLargeScreen
171                 ? 0
172                 : (isPortrait
173                         ? getDimenByName(systemRes, NAVBAR_HEIGHT)
174                         : (isGesture
175                                 ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE)
176                                 : 0));
177         int leftNav = navInsets.left;
178         int rightNav = navInsets.right;
179         if (!isLargeScreen && !isGesture && !isPortrait) {
180             // In 3-button landscape/seascape, Launcher should always have nav insets regardless if
181             // it's initiated from fullscreen apps.
182             int navBarWidth = getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
183             switch (getRotation(context)) {
184                 case Surface.ROTATION_90 -> rightNav = navBarWidth;
185                 case Surface.ROTATION_270 -> leftNav = navBarWidth;
186             }
187         }
188         Insets newNavInsets = Insets.of(leftNav, navInsets.top, rightNav, bottomNav);
189         insetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
190         insetsBuilder.setInsetsIgnoringVisibility(WindowInsets.Type.navigationBars(), newNavInsets);
191 
192         Insets statusBarInsets = oldInsets.getInsets(WindowInsets.Type.statusBars());
193 
194         Insets newStatusBarInsets = Insets.of(
195                 statusBarInsets.left,
196                 getStatusBarHeight(context, isPortrait, statusBarInsets.top),
197                 statusBarInsets.right,
198                 statusBarInsets.bottom);
199         insetsBuilder.setInsets(WindowInsets.Type.statusBars(), newStatusBarInsets);
200         insetsBuilder.setInsetsIgnoringVisibility(
201                 WindowInsets.Type.statusBars(), newStatusBarInsets);
202 
203         // Override the tappable insets to be 0 on the bottom for gesture nav (otherwise taskbar
204         // would count towards it). This is used for the bottom protection in All Apps for example.
205         if (isGesture) {
206             Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
207             Insets newTappableInsets = Insets.of(oldTappableInsets.left, oldTappableInsets.top,
208                     oldTappableInsets.right, 0);
209             insetsBuilder.setInsets(WindowInsets.Type.tappableElement(), newTappableInsets);
210         }
211 
212         applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
213                 context, isLargeScreen, dpToPx(config.screenWidthDp), oldInsets, insetsBuilder);
214 
215         WindowInsets result = insetsBuilder.build();
216         Insets systemWindowInsets = result.getInsetsIgnoringVisibility(
217                 WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
218         outInsets.set(systemWindowInsets.left, systemWindowInsets.top, systemWindowInsets.right,
219                 systemWindowInsets.bottom);
220         return result;
221     }
222 
223     /**
224      * For large screen, when display cutout is at bottom left/right corner of screen, override
225      * display cutout's bottom inset to 0, because launcher allows drawing content over that area.
226      */
applyDisplayCutoutBottomInsetOverrideOnLargeScreen( @onNull Context context, boolean isLargeScreen, int screenWidthPx, @NonNull WindowInsets windowInsets, @NonNull WindowInsets.Builder insetsBuilder)227     private static void applyDisplayCutoutBottomInsetOverrideOnLargeScreen(
228             @NonNull Context context,
229             boolean isLargeScreen,
230             int screenWidthPx,
231             @NonNull WindowInsets windowInsets,
232             @NonNull WindowInsets.Builder insetsBuilder) {
233         if (!isLargeScreen) {
234             return;
235         }
236 
237         final DisplayCutout displayCutout = windowInsets.getDisplayCutout();
238         if (displayCutout == null) {
239             return;
240         }
241 
242         if (!areBottomDisplayCutoutsSmallAndAtCorners(
243                 displayCutout.getBoundingRectBottom(), screenWidthPx, context.getResources())) {
244             return;
245         }
246 
247         Insets oldDisplayCutoutInset = windowInsets.getInsets(WindowInsets.Type.displayCutout());
248         Insets newDisplayCutoutInset = Insets.of(
249                 oldDisplayCutoutInset.left,
250                 oldDisplayCutoutInset.top,
251                 oldDisplayCutoutInset.right,
252                 0);
253         insetsBuilder.setInsetsIgnoringVisibility(
254                 WindowInsets.Type.displayCutout(), newDisplayCutoutInset);
255     }
256 
257     /**
258      * @see doc at {@link #areBottomDisplayCutoutsSmallAndAtCorners(Rect, int, int)}
259      */
areBottomDisplayCutoutsSmallAndAtCorners( @onNull Rect cutoutRectBottom, int screenWidthPx, @NonNull Resources res)260     private static boolean areBottomDisplayCutoutsSmallAndAtCorners(
261             @NonNull Rect cutoutRectBottom, int screenWidthPx, @NonNull Resources res) {
262         return areBottomDisplayCutoutsSmallAndAtCorners(cutoutRectBottom, screenWidthPx,
263                 res.getDimensionPixelSize(R.dimen.max_width_and_height_of_small_display_cutout));
264     }
265 
266     /**
267      * Return true if bottom display cutouts are at bottom left/right corners, AND has width or
268      * height <= maxWidthAndHeightOfSmallCutoutPx. Note that display cutout rect and screenWidthPx
269      * passed to this method should be in the SAME screen rotation.
270      *
271      * @param cutoutRectBottom bottom display cutout rect, this is based on current screen rotation
272      * @param screenWidthPx screen width in px based on current screen rotation
273      * @param maxWidthAndHeightOfSmallCutoutPx maximum width and height pixels of cutout.
274      */
275     @VisibleForTesting
areBottomDisplayCutoutsSmallAndAtCorners( @onNull Rect cutoutRectBottom, int screenWidthPx, int maxWidthAndHeightOfSmallCutoutPx)276     static boolean areBottomDisplayCutoutsSmallAndAtCorners(
277             @NonNull Rect cutoutRectBottom, int screenWidthPx,
278             int maxWidthAndHeightOfSmallCutoutPx) {
279         // Empty cutoutRectBottom means there is no display cutout at the bottom. We should ignore
280         // it by returning false.
281         if (cutoutRectBottom.isEmpty()) {
282             return false;
283         }
284         return (cutoutRectBottom.right <= maxWidthAndHeightOfSmallCutoutPx)
285                 || cutoutRectBottom.left >= (screenWidthPx - maxWidthAndHeightOfSmallCutoutPx);
286     }
287 
getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset)288     protected int getStatusBarHeight(Context context, boolean isPortrait, int statusBarInset) {
289         Resources systemRes = context.getResources();
290         int statusBarHeight = getDimenByName(systemRes,
291                 isPortrait ? STATUS_BAR_HEIGHT_PORTRAIT : STATUS_BAR_HEIGHT_LANDSCAPE,
292                 STATUS_BAR_HEIGHT);
293 
294         return Math.max(statusBarInset, statusBarHeight);
295     }
296 
297     /**
298      * Returns a list of possible WindowBounds for the display keyed on the 4 surface rotations
299      */
estimateWindowBounds(Context context, final CachedDisplayInfo displayInfo)300     protected List<WindowBounds> estimateWindowBounds(Context context,
301             final CachedDisplayInfo displayInfo) {
302         int densityDpi = context.getResources().getConfiguration().densityDpi;
303         final int rotation = displayInfo.rotation;
304 
305         int minSize = Math.min(displayInfo.size.x, displayInfo.size.y);
306         int swDp = (int) dpiFromPx(minSize, densityDpi);
307 
308         Resources systemRes;
309         {
310             Configuration conf = new Configuration();
311             conf.smallestScreenWidthDp = swDp;
312             systemRes = context.createConfigurationContext(conf).getResources();
313         }
314 
315         boolean isTablet = swDp >= MIN_TABLET_WIDTH;
316         boolean isTabletOrGesture = isTablet || isGestureNav(context);
317 
318         // Use the status bar height resources because current system API to get the status bar
319         // height doesn't allow to do this for an arbitrary display, it returns value only
320         // for the current active display (see com.android.internal.policy.StatusBarUtils)
321         int statusBarHeightPortrait = getDimenByName(systemRes,
322                 STATUS_BAR_HEIGHT_PORTRAIT, STATUS_BAR_HEIGHT);
323         int statusBarHeightLandscape = getDimenByName(systemRes,
324                 STATUS_BAR_HEIGHT_LANDSCAPE, STATUS_BAR_HEIGHT);
325 
326         int navBarHeightPortrait, navBarHeightLandscape, navbarWidthLandscape;
327 
328         navBarHeightPortrait = isTablet
329                 ? (mTaskbarDrawnInProcess
330                         ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
331                 : getDimenByName(systemRes, NAVBAR_HEIGHT);
332 
333         navBarHeightLandscape = isTablet
334                 ? (mTaskbarDrawnInProcess
335                         ? 0 : context.getResources().getDimensionPixelSize(R.dimen.taskbar_size))
336                 : (isTabletOrGesture
337                         ? getDimenByName(systemRes, NAVBAR_HEIGHT_LANDSCAPE) : 0);
338         navbarWidthLandscape = isTabletOrGesture
339                 ? 0
340                 : getDimenByName(systemRes, NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
341 
342         List<WindowBounds> result = new ArrayList<>(4);
343         Point tempSize = new Point();
344         for (int i = 0; i < 4; i++) {
345             int rotationChange = deltaRotation(rotation, i);
346             tempSize.set(displayInfo.size.x, displayInfo.size.y);
347             rotateSize(tempSize, rotationChange);
348             Rect bounds = new Rect(0, 0, tempSize.x, tempSize.y);
349 
350             int navBarHeight, navbarWidth, statusBarHeight;
351             if (tempSize.y > tempSize.x) {
352                 navBarHeight = navBarHeightPortrait;
353                 navbarWidth = 0;
354                 statusBarHeight = statusBarHeightPortrait;
355             } else {
356                 navBarHeight = navBarHeightLandscape;
357                 navbarWidth = navbarWidthLandscape;
358                 statusBarHeight = statusBarHeightLandscape;
359             }
360 
361             DisplayCutout rotatedCutout = rotateCutout(
362                     displayInfo.cutout, displayInfo.size.x, displayInfo.size.y, rotation, i);
363             Rect insets = getSafeInsets(rotatedCutout);
364             if (areBottomDisplayCutoutsSmallAndAtCorners(
365                     rotatedCutout.getBoundingRectBottom(),
366                     bounds.width(),
367                     context.getResources())) {
368                 insets.bottom = 0;
369             }
370             insets.top = Math.max(insets.top, statusBarHeight);
371             insets.bottom = Math.max(insets.bottom, navBarHeight);
372 
373             if (i == Surface.ROTATION_270 || i == Surface.ROTATION_180) {
374                 // On reverse landscape (and in rare-case when the natural orientation of the
375                 // device is landscape), navigation bar is on the right.
376                 insets.left = Math.max(insets.left, navbarWidth);
377             } else {
378                 insets.right = Math.max(insets.right, navbarWidth);
379             }
380             result.add(new WindowBounds(bounds, insets, i));
381         }
382         return result;
383     }
384 
385     /**
386      * Wrapper around the utility method for easier emulation
387      */
getDimenByName(Resources res, String resName)388     protected int getDimenByName(Resources res, String resName) {
389         return ResourceUtils.getDimenByName(resName, res, 0);
390     }
391 
392     /**
393      * Wrapper around the utility method for easier emulation
394      */
getDimenByName(Resources res, String resName, String fallback)395     protected int getDimenByName(Resources res, String resName, String fallback) {
396         int dimen = ResourceUtils.getDimenByName(resName, res, -1);
397         return dimen > -1 ? dimen : getDimenByName(res, fallback);
398     }
399 
isGestureNav(Context context)400     protected boolean isGestureNav(Context context) {
401         return ResourceUtils.getIntegerByName("config_navBarInteractionMode",
402                 context.getResources(), INVALID_RESOURCE_HANDLE) == 2;
403     }
404 
405     /**
406      * Returns a CachedDisplayInfo initialized for the current display
407      */
getDisplayInfo(Context displayInfoContext)408     public CachedDisplayInfo getDisplayInfo(Context displayInfoContext) {
409         int rotation = getRotation(displayInfoContext);
410         WindowMetrics windowMetrics = displayInfoContext.getSystemService(WindowManager.class)
411                 .getMaximumWindowMetrics();
412         return getDisplayInfo(windowMetrics, rotation);
413     }
414 
415     /**
416      * Returns a CachedDisplayInfo initialized for the current display
417      */
getDisplayInfo(WindowMetrics windowMetrics, int rotation)418     protected CachedDisplayInfo getDisplayInfo(WindowMetrics windowMetrics, int rotation) {
419         Point size = new Point(windowMetrics.getBounds().right, windowMetrics.getBounds().bottom);
420         return new CachedDisplayInfo(size, rotation,
421                 windowMetrics.getWindowInsets().getDisplayCutout());
422     }
423 
424     /**
425      * Returns bounds of the display associated with the context, or bounds of DEFAULT_DISPLAY
426      * if the context isn't associated with a display.
427      */
getCurrentBounds(Context displayInfoContext)428     public Rect getCurrentBounds(Context displayInfoContext) {
429         Resources res = displayInfoContext.getResources();
430         Configuration config = res.getConfiguration();
431 
432         float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
433         float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
434 
435         return new Rect(0, 0, (int) screenWidth, (int) screenHeight);
436     }
437 
438     /**
439      * Returns rotation of the display associated with the context, or rotation of DEFAULT_DISPLAY
440      * if the context isn't associated with a display.
441      */
getRotation(Context displayInfoContext)442     public int getRotation(Context displayInfoContext) {
443         return getDisplay(displayInfoContext).getRotation();
444     }
445 
446     /**
447      * Returns the display associated with the context, or DEFAULT_DISPLAY if the context isn't
448      * associated with a display.
449      */
getDisplay(Context displayInfoContext)450     protected Display getDisplay(Context displayInfoContext) {
451         try {
452             return displayInfoContext.getDisplay();
453         } catch (UnsupportedOperationException e) {
454             // Ignore
455         }
456         return displayInfoContext.getSystemService(DisplayManager.class).getDisplay(
457                 DEFAULT_DISPLAY);
458     }
459 
460     /**
461      * Returns a DisplayCutout which represents a rotated version of the original
462      */
rotateCutout(DisplayCutout original, int startWidth, int startHeight, int fromRotation, int toRotation)463     protected DisplayCutout rotateCutout(DisplayCutout original, int startWidth, int startHeight,
464             int fromRotation, int toRotation) {
465         Rect safeCutout = getSafeInsets(original);
466         rotateRect(safeCutout, deltaRotation(fromRotation, toRotation));
467         return new DisplayCutout(Insets.of(safeCutout), null, null, null, null);
468     }
469 
470     /**
471      * Returns the current navigation mode from resource.
472      */
getNavigationMode(Context context)473     public NavigationMode getNavigationMode(Context context) {
474         int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
475                 context.getResources(), INVALID_RESOURCE_HANDLE);
476 
477         if (modeInt == INVALID_RESOURCE_HANDLE) {
478             Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
479         } else {
480             for (NavigationMode m : NavigationMode.values()) {
481                 if (m.resValue == modeInt) {
482                     return m;
483                 }
484             }
485         }
486         return NavigationMode.NO_BUTTON;
487     }
488 
489     /**
490      * @see DisplayCutout#getSafeInsets
491      */
getSafeInsets(DisplayCutout cutout)492     public static Rect getSafeInsets(DisplayCutout cutout) {
493         return new Rect(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
494                 cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
495     }
496 
497     /** Registers a listener for Taskbar changes in Desktop Mode.  */
registerDesktopVisibilityListener(DesktopVisibilityListener listener)498     public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { }
499 
500     /** Removes a previously registered listener for Taskbar changes in Desktop Mode.  */
unregisterDesktopVisibilityListener(DesktopVisibilityListener listener)501     public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { }
502 
503     /** A listener for when the user enters/exits Desktop Mode.  */
504     public interface DesktopVisibilityListener {
505         /**
506          * Called when the desktop mode state on the display whose ID is `displayId` changes.
507          *
508          * @param displayId The ID of the display for which this notification is triggering.
509          * @param isInDesktopModeAndNotInOverview True if a desktop is currently active on the given
510          *                                        display, and Overview is currently inactive.
511          */
onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview)512         default void onIsInDesktopModeChanged(int displayId,
513                 boolean isInDesktopModeAndNotInOverview) {
514         }
515 
516         /**
517          * Called whenever the conditions that allow the creation of desks change.
518          *
519          * @param canCreateDesks whether it is possible to create new desks.
520          */
onCanCreateDesksChanged(boolean canCreateDesks)521         default void onCanCreateDesksChanged(boolean canCreateDesks) {
522         }
523 
524         /**
525          * Called when a new desk is added.
526          *
527          * @param displayId The ID of the display on which the desk was added.
528          * @param deskId The ID of the newly added desk.
529          */
onDeskAdded(int displayId, int deskId)530         default void onDeskAdded(int displayId, int deskId) {}
531 
532         /**
533          * Called when an existing desk is removed.
534          *
535          * @param displayId The ID of the display on which the desk was removed.
536          * @param deskId The ID of the desk that was removed.
537          */
onDeskRemoved(int displayId, int deskId)538         default void onDeskRemoved(int displayId, int deskId) {}
539 
540         /**
541          * Called when the active desk changes.
542          *
543          * @param displayId The ID of the display on which the desk activation change is happening.
544          * @param newActiveDesk The ID of the new active desk or -1 if no desk is active anymore
545          *                      (i.e. exit desktop mode).
546          * @param oldActiveDesk The ID of the desk that was previously active, or -1 if no desk was
547          *                      active before.
548          */
onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk)549         default void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {}
550     }
551 
552 }
553