1 /* 2 * Copyright (C) 2017 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 android.app; 18 19 import static android.app.ActivityThread.isSystem; 20 import static android.app.WindowConfigurationProto.ACTIVITY_TYPE; 21 import static android.app.WindowConfigurationProto.APP_BOUNDS; 22 import static android.app.WindowConfigurationProto.WINDOWING_MODE; 23 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.TestApi; 27 import android.content.res.Configuration; 28 import android.graphics.Rect; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.util.proto.ProtoOutputStream; 32 import android.view.DisplayInfo; 33 34 /** 35 * Class that contains windowing configuration/state for other objects that contain windows directly 36 * or indirectly. E.g. Activities, Task, Displays, ... 37 * The test class is {@link com.android.server.wm.WindowConfigurationTests} which must be kept 38 * up-to-date and ran anytime changes are made to this class. 39 * @hide 40 */ 41 @TestApi 42 public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> { 43 /** 44 * bounds that can differ from app bounds, which may include things such as insets. 45 * 46 * TODO: Investigate combining with {@link mAppBounds}. Can the latter be a product of the 47 * former? 48 */ 49 private Rect mBounds = new Rect(); 50 51 /** 52 * {@link android.graphics.Rect} defining app bounds. The dimensions override usages of 53 * {@link DisplayInfo#appHeight} and {@link DisplayInfo#appWidth} and mirrors these values at 54 * the display level. Lower levels can override these values to provide custom bounds to enforce 55 * features such as a max aspect ratio. 56 */ 57 private Rect mAppBounds; 58 59 /** The current windowing mode of the configuration. */ 60 private @WindowingMode int mWindowingMode; 61 62 /** Windowing mode is currently not defined. */ 63 public static final int WINDOWING_MODE_UNDEFINED = 0; 64 /** Occupies the full area of the screen or the parent container. */ 65 public static final int WINDOWING_MODE_FULLSCREEN = 1; 66 /** Always on-top (always visible). of other siblings in its parent container. */ 67 public static final int WINDOWING_MODE_PINNED = 2; 68 /** The primary container driving the screen to be in split-screen mode. */ 69 public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3; 70 /** 71 * The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in 72 * split-screen mode. 73 * NOTE: Containers launched with the windowing mode with APIs like 74 * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in 75 * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing 76 * mode 77 * @see #WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY 78 */ 79 public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4; 80 /** 81 * Alias for {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} that makes it clear that the usage 82 * points for APIs like {@link ActivityOptions#setLaunchWindowingMode(int)} that the container 83 * will launch into fullscreen or split-screen secondary depending on if the device is currently 84 * in fullscreen mode or split-screen mode. 85 */ 86 public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY = 87 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 88 /** Can be freely resized within its parent container. */ 89 public static final int WINDOWING_MODE_FREEFORM = 5; 90 91 /** @hide */ 92 @IntDef(prefix = { "WINDOWING_MODE_" }, value = { 93 WINDOWING_MODE_UNDEFINED, 94 WINDOWING_MODE_FULLSCREEN, 95 WINDOWING_MODE_PINNED, 96 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, 97 WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, 98 WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, 99 WINDOWING_MODE_FREEFORM, 100 }) 101 public @interface WindowingMode {} 102 103 /** The current activity type of the configuration. */ 104 private @ActivityType int mActivityType; 105 106 /** Activity type is currently not defined. */ 107 public static final int ACTIVITY_TYPE_UNDEFINED = 0; 108 /** Standard activity type. Nothing special about the activity... */ 109 public static final int ACTIVITY_TYPE_STANDARD = 1; 110 /** Home/Launcher activity type. */ 111 public static final int ACTIVITY_TYPE_HOME = 2; 112 /** Recents/Overview activity type. There is only one activity with this type in the system. */ 113 public static final int ACTIVITY_TYPE_RECENTS = 3; 114 /** Assistant activity type. */ 115 public static final int ACTIVITY_TYPE_ASSISTANT = 4; 116 117 /** @hide */ 118 @IntDef(prefix = { "ACTIVITY_TYPE_" }, value = { 119 ACTIVITY_TYPE_UNDEFINED, 120 ACTIVITY_TYPE_STANDARD, 121 ACTIVITY_TYPE_HOME, 122 ACTIVITY_TYPE_RECENTS, 123 ACTIVITY_TYPE_ASSISTANT, 124 }) 125 public @interface ActivityType {} 126 127 /** Bit that indicates that the {@link #mBounds} changed. 128 * @hide */ 129 public static final int WINDOW_CONFIG_BOUNDS = 1 << 0; 130 /** Bit that indicates that the {@link #mAppBounds} changed. 131 * @hide */ 132 public static final int WINDOW_CONFIG_APP_BOUNDS = 1 << 1; 133 /** Bit that indicates that the {@link #mWindowingMode} changed. 134 * @hide */ 135 public static final int WINDOW_CONFIG_WINDOWING_MODE = 1 << 2; 136 /** Bit that indicates that the {@link #mActivityType} changed. 137 * @hide */ 138 public static final int WINDOW_CONFIG_ACTIVITY_TYPE = 1 << 3; 139 140 /** @hide */ 141 @IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = { 142 WINDOW_CONFIG_BOUNDS, 143 WINDOW_CONFIG_APP_BOUNDS, 144 WINDOW_CONFIG_WINDOWING_MODE, 145 WINDOW_CONFIG_ACTIVITY_TYPE 146 }) 147 public @interface WindowConfig {} 148 149 /** @hide */ 150 public static final int PINNED_WINDOWING_MODE_ELEVATION_IN_DIP = 5; 151 WindowConfiguration()152 public WindowConfiguration() { 153 unset(); 154 } 155 156 /** @hide */ WindowConfiguration(WindowConfiguration configuration)157 public WindowConfiguration(WindowConfiguration configuration) { 158 setTo(configuration); 159 } 160 WindowConfiguration(Parcel in)161 private WindowConfiguration(Parcel in) { 162 readFromParcel(in); 163 } 164 165 @Override writeToParcel(Parcel dest, int flags)166 public void writeToParcel(Parcel dest, int flags) { 167 dest.writeParcelable(mBounds, flags); 168 dest.writeParcelable(mAppBounds, flags); 169 dest.writeInt(mWindowingMode); 170 dest.writeInt(mActivityType); 171 } 172 readFromParcel(Parcel source)173 private void readFromParcel(Parcel source) { 174 mBounds = source.readParcelable(Rect.class.getClassLoader()); 175 mAppBounds = source.readParcelable(Rect.class.getClassLoader()); 176 mWindowingMode = source.readInt(); 177 mActivityType = source.readInt(); 178 } 179 180 @Override describeContents()181 public int describeContents() { 182 return 0; 183 } 184 185 /** @hide */ 186 public static final Creator<WindowConfiguration> CREATOR = new Creator<WindowConfiguration>() { 187 @Override 188 public WindowConfiguration createFromParcel(Parcel in) { 189 return new WindowConfiguration(in); 190 } 191 192 @Override 193 public WindowConfiguration[] newArray(int size) { 194 return new WindowConfiguration[size]; 195 } 196 }; 197 198 /** 199 * Sets the bounds to the provided {@link Rect}. 200 * @param rect the new bounds value. 201 */ setBounds(Rect rect)202 public void setBounds(Rect rect) { 203 if (rect == null) { 204 mBounds.setEmpty(); 205 return; 206 } 207 208 mBounds.set(rect); 209 } 210 211 /** 212 * Set {@link #mAppBounds} to the input Rect. 213 * @param rect The rect value to set {@link #mAppBounds} to. 214 * @see #getAppBounds() 215 */ setAppBounds(Rect rect)216 public void setAppBounds(Rect rect) { 217 if (rect == null) { 218 mAppBounds = null; 219 return; 220 } 221 222 setAppBounds(rect.left, rect.top, rect.right, rect.bottom); 223 } 224 225 /** 226 * @see #setAppBounds(Rect) 227 * @see #getAppBounds() 228 * @hide 229 */ setAppBounds(int left, int top, int right, int bottom)230 public void setAppBounds(int left, int top, int right, int bottom) { 231 if (mAppBounds == null) { 232 mAppBounds = new Rect(); 233 } 234 235 mAppBounds.set(left, top, right, bottom); 236 } 237 238 /** @see #setAppBounds(Rect) */ getAppBounds()239 public Rect getAppBounds() { 240 return mAppBounds; 241 } 242 243 /** @see #setBounds(Rect) */ getBounds()244 public Rect getBounds() { 245 return mBounds; 246 } 247 setWindowingMode(@indowingMode int windowingMode)248 public void setWindowingMode(@WindowingMode int windowingMode) { 249 mWindowingMode = windowingMode; 250 } 251 252 @WindowingMode getWindowingMode()253 public int getWindowingMode() { 254 return mWindowingMode; 255 } 256 setActivityType(@ctivityType int activityType)257 public void setActivityType(@ActivityType int activityType) { 258 if (mActivityType == activityType) { 259 return; 260 } 261 262 // Error check within system server that we are not changing activity type which can be 263 // dangerous. It is okay for things to change in the application process as it doesn't 264 // affect how other things is the system is managed. 265 if (isSystem() 266 && mActivityType != ACTIVITY_TYPE_UNDEFINED 267 && activityType != ACTIVITY_TYPE_UNDEFINED) { 268 throw new IllegalStateException("Can't change activity type once set: " + this 269 + " activityType=" + activityTypeToString(activityType)); 270 } 271 mActivityType = activityType; 272 } 273 274 @ActivityType getActivityType()275 public int getActivityType() { 276 return mActivityType; 277 } 278 setTo(WindowConfiguration other)279 public void setTo(WindowConfiguration other) { 280 setBounds(other.mBounds); 281 setAppBounds(other.mAppBounds); 282 setWindowingMode(other.mWindowingMode); 283 setActivityType(other.mActivityType); 284 } 285 286 /** Set this object to completely undefined. 287 * @hide */ unset()288 public void unset() { 289 setToDefaults(); 290 } 291 292 /** @hide */ setToDefaults()293 public void setToDefaults() { 294 setAppBounds(null); 295 setBounds(null); 296 setWindowingMode(WINDOWING_MODE_UNDEFINED); 297 setActivityType(ACTIVITY_TYPE_UNDEFINED); 298 } 299 300 /** 301 * Copies the fields from delta into this Configuration object, keeping 302 * track of which ones have changed. Any undefined fields in {@code delta} 303 * are ignored and not copied in to the current Configuration. 304 * 305 * @return a bit mask of the changed fields, as per {@link #diff} 306 * @hide 307 */ updateFrom(@onNull WindowConfiguration delta)308 public @WindowConfig int updateFrom(@NonNull WindowConfiguration delta) { 309 int changed = 0; 310 // Only allow override if bounds is not empty 311 if (!delta.mBounds.isEmpty() && !delta.mBounds.equals(mBounds)) { 312 changed |= WINDOW_CONFIG_BOUNDS; 313 setBounds(delta.mBounds); 314 } 315 if (delta.mAppBounds != null && !delta.mAppBounds.equals(mAppBounds)) { 316 changed |= WINDOW_CONFIG_APP_BOUNDS; 317 setAppBounds(delta.mAppBounds); 318 } 319 if (delta.mWindowingMode != WINDOWING_MODE_UNDEFINED 320 && mWindowingMode != delta.mWindowingMode) { 321 changed |= WINDOW_CONFIG_WINDOWING_MODE; 322 setWindowingMode(delta.mWindowingMode); 323 } 324 if (delta.mActivityType != ACTIVITY_TYPE_UNDEFINED 325 && mActivityType != delta.mActivityType) { 326 changed |= WINDOW_CONFIG_ACTIVITY_TYPE; 327 setActivityType(delta.mActivityType); 328 } 329 return changed; 330 } 331 332 /** 333 * Return a bit mask of the differences between this Configuration object and the given one. 334 * Does not change the values of either. Any undefined fields in <var>other</var> are ignored. 335 * @param other The configuration to diff against. 336 * @param compareUndefined If undefined values should be compared. 337 * @return Returns a bit mask indicating which configuration 338 * values has changed, containing any combination of {@link WindowConfig} flags. 339 * 340 * @see Configuration#diff(Configuration) 341 * @hide 342 */ diff(WindowConfiguration other, boolean compareUndefined)343 public @WindowConfig long diff(WindowConfiguration other, boolean compareUndefined) { 344 long changes = 0; 345 346 if (!mBounds.equals(other.mBounds)) { 347 changes |= WINDOW_CONFIG_BOUNDS; 348 } 349 350 // Make sure that one of the values is not null and that they are not equal. 351 if ((compareUndefined || other.mAppBounds != null) 352 && mAppBounds != other.mAppBounds 353 && (mAppBounds == null || !mAppBounds.equals(other.mAppBounds))) { 354 changes |= WINDOW_CONFIG_APP_BOUNDS; 355 } 356 357 if ((compareUndefined || other.mWindowingMode != WINDOWING_MODE_UNDEFINED) 358 && mWindowingMode != other.mWindowingMode) { 359 changes |= WINDOW_CONFIG_WINDOWING_MODE; 360 } 361 362 if ((compareUndefined || other.mActivityType != ACTIVITY_TYPE_UNDEFINED) 363 && mActivityType != other.mActivityType) { 364 changes |= WINDOW_CONFIG_ACTIVITY_TYPE; 365 } 366 367 return changes; 368 } 369 370 @Override compareTo(WindowConfiguration that)371 public int compareTo(WindowConfiguration that) { 372 int n = 0; 373 if (mAppBounds == null && that.mAppBounds != null) { 374 return 1; 375 } else if (mAppBounds != null && that.mAppBounds == null) { 376 return -1; 377 } else if (mAppBounds != null && that.mAppBounds != null) { 378 n = mAppBounds.left - that.mAppBounds.left; 379 if (n != 0) return n; 380 n = mAppBounds.top - that.mAppBounds.top; 381 if (n != 0) return n; 382 n = mAppBounds.right - that.mAppBounds.right; 383 if (n != 0) return n; 384 n = mAppBounds.bottom - that.mAppBounds.bottom; 385 if (n != 0) return n; 386 } 387 388 n = mBounds.left - that.mBounds.left; 389 if (n != 0) return n; 390 n = mBounds.top - that.mBounds.top; 391 if (n != 0) return n; 392 n = mBounds.right - that.mBounds.right; 393 if (n != 0) return n; 394 n = mBounds.bottom - that.mBounds.bottom; 395 if (n != 0) return n; 396 397 n = mWindowingMode - that.mWindowingMode; 398 if (n != 0) return n; 399 n = mActivityType - that.mActivityType; 400 if (n != 0) return n; 401 402 // if (n != 0) return n; 403 return n; 404 } 405 406 /** @hide */ 407 @Override equals(Object that)408 public boolean equals(Object that) { 409 if (that == null) return false; 410 if (that == this) return true; 411 if (!(that instanceof WindowConfiguration)) { 412 return false; 413 } 414 return this.compareTo((WindowConfiguration) that) == 0; 415 } 416 417 /** @hide */ 418 @Override hashCode()419 public int hashCode() { 420 int result = 0; 421 if (mAppBounds != null) { 422 result = 31 * result + mAppBounds.hashCode(); 423 } 424 result = 31 * result + mBounds.hashCode(); 425 426 result = 31 * result + mWindowingMode; 427 result = 31 * result + mActivityType; 428 return result; 429 } 430 431 /** @hide */ 432 @Override toString()433 public String toString() { 434 return "{ mBounds=" + mBounds 435 + " mAppBounds=" + mAppBounds 436 + " mWindowingMode=" + windowingModeToString(mWindowingMode) 437 + " mActivityType=" + activityTypeToString(mActivityType) + "}"; 438 } 439 440 /** 441 * Write to a protocol buffer output stream. 442 * Protocol buffer message definition at {@link android.app.WindowConfigurationProto} 443 * 444 * @param protoOutputStream Stream to write the WindowConfiguration object to. 445 * @param fieldId Field Id of the WindowConfiguration as defined in the parent message 446 * @hide 447 */ writeToProto(ProtoOutputStream protoOutputStream, long fieldId)448 public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) { 449 final long token = protoOutputStream.start(fieldId); 450 if (mAppBounds != null) { 451 mAppBounds.writeToProto(protoOutputStream, APP_BOUNDS); 452 } 453 protoOutputStream.write(WINDOWING_MODE, mWindowingMode); 454 protoOutputStream.write(ACTIVITY_TYPE, mActivityType); 455 protoOutputStream.end(token); 456 } 457 458 /** 459 * Returns true if the activities associated with this window configuration display a shadow 460 * around their border. 461 * @hide 462 */ hasWindowShadow()463 public boolean hasWindowShadow() { 464 return tasksAreFloating(); 465 } 466 467 /** 468 * Returns true if the activities associated with this window configuration display a decor 469 * view. 470 * @hide 471 */ hasWindowDecorCaption()472 public boolean hasWindowDecorCaption() { 473 return mWindowingMode == WINDOWING_MODE_FREEFORM; 474 } 475 476 /** 477 * Returns true if the tasks associated with this window configuration can be resized 478 * independently of their parent container. 479 * @hide 480 */ canResizeTask()481 public boolean canResizeTask() { 482 return mWindowingMode == WINDOWING_MODE_FREEFORM; 483 } 484 485 /** Returns true if the task bounds should persist across power cycles. 486 * @hide */ persistTaskBounds()487 public boolean persistTaskBounds() { 488 return mWindowingMode == WINDOWING_MODE_FREEFORM; 489 } 490 491 /** 492 * Returns true if the tasks associated with this window configuration are floating. 493 * Floating tasks are laid out differently as they are allowed to extend past the display bounds 494 * without overscan insets. 495 * @hide 496 */ tasksAreFloating()497 public boolean tasksAreFloating() { 498 return isFloating(mWindowingMode); 499 } 500 501 /** 502 * Returns true if the windowingMode represents a floating window. 503 * @hide 504 */ isFloating(int windowingMode)505 public static boolean isFloating(int windowingMode) { 506 return windowingMode == WINDOWING_MODE_FREEFORM || windowingMode == WINDOWING_MODE_PINNED; 507 } 508 509 /** 510 * Returns true if the windows associated with this window configuration can receive input keys. 511 * @hide 512 */ canReceiveKeys()513 public boolean canReceiveKeys() { 514 return mWindowingMode != WINDOWING_MODE_PINNED; 515 } 516 517 /** 518 * Returns true if the container associated with this window configuration is always-on-top of 519 * its siblings. 520 * @hide 521 */ isAlwaysOnTop()522 public boolean isAlwaysOnTop() { 523 return mWindowingMode == WINDOWING_MODE_PINNED; 524 } 525 526 /** 527 * Returns true if any visible windows belonging to apps with this window configuration should 528 * be kept on screen when the app is killed due to something like the low memory killer. 529 * @hide 530 */ keepVisibleDeadAppWindowOnScreen()531 public boolean keepVisibleDeadAppWindowOnScreen() { 532 return mWindowingMode != WINDOWING_MODE_PINNED; 533 } 534 535 /** 536 * Returns true if the backdrop on the client side should match the frame of the window. 537 * Returns false, if the backdrop should be fullscreen. 538 * @hide 539 */ useWindowFrameForBackdrop()540 public boolean useWindowFrameForBackdrop() { 541 return mWindowingMode == WINDOWING_MODE_FREEFORM || mWindowingMode == WINDOWING_MODE_PINNED; 542 } 543 544 /** 545 * Returns true if this container may be scaled without resizing, and windows within may need 546 * to be configured as such. 547 * @hide 548 */ windowsAreScaleable()549 public boolean windowsAreScaleable() { 550 return mWindowingMode == WINDOWING_MODE_PINNED; 551 } 552 553 /** 554 * Returns true if windows in this container should be given move animations by default. 555 * @hide 556 */ hasMovementAnimations()557 public boolean hasMovementAnimations() { 558 return mWindowingMode != WINDOWING_MODE_PINNED; 559 } 560 561 /** 562 * Returns true if this container can be put in either 563 * {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or 564 * {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on its current state. 565 * @hide 566 */ supportSplitScreenWindowingMode()567 public boolean supportSplitScreenWindowingMode() { 568 return supportSplitScreenWindowingMode(mActivityType); 569 } 570 571 /** @hide */ supportSplitScreenWindowingMode(int activityType)572 public static boolean supportSplitScreenWindowingMode(int activityType) { 573 return activityType != ACTIVITY_TYPE_ASSISTANT; 574 } 575 576 /** @hide */ windowingModeToString(@indowingMode int windowingMode)577 public static String windowingModeToString(@WindowingMode int windowingMode) { 578 switch (windowingMode) { 579 case WINDOWING_MODE_UNDEFINED: return "undefined"; 580 case WINDOWING_MODE_FULLSCREEN: return "fullscreen"; 581 case WINDOWING_MODE_PINNED: return "pinned"; 582 case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary"; 583 case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary"; 584 case WINDOWING_MODE_FREEFORM: return "freeform"; 585 } 586 return String.valueOf(windowingMode); 587 } 588 589 /** @hide */ activityTypeToString(@ctivityType int applicationType)590 public static String activityTypeToString(@ActivityType int applicationType) { 591 switch (applicationType) { 592 case ACTIVITY_TYPE_UNDEFINED: return "undefined"; 593 case ACTIVITY_TYPE_STANDARD: return "standard"; 594 case ACTIVITY_TYPE_HOME: return "home"; 595 case ACTIVITY_TYPE_RECENTS: return "recents"; 596 case ACTIVITY_TYPE_ASSISTANT: return "assistant"; 597 } 598 return String.valueOf(applicationType); 599 } 600 } 601