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