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 com.android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 25 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 26 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 27 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 28 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 29 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 30 import static android.app.WindowConfiguration.activityTypeToString; 31 import static android.app.WindowConfiguration.windowingModeToString; 32 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; 33 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; 34 import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION; 35 36 import android.annotation.CallSuper; 37 import android.app.WindowConfiguration; 38 import android.content.res.Configuration; 39 import android.graphics.Rect; 40 import android.util.proto.ProtoOutputStream; 41 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 45 /** 46 * Contains common logic for classes that have override configurations and are organized in a 47 * hierarchy. 48 */ 49 public abstract class ConfigurationContainer<E extends ConfigurationContainer> { 50 /** 51 * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being 52 * set directly. 53 */ 54 private Rect mReturnBounds = new Rect(); 55 56 /** Contains override configuration settings applied to this configuration container. */ 57 private Configuration mOverrideConfiguration = new Configuration(); 58 59 /** True if mOverrideConfiguration is not empty */ 60 private boolean mHasOverrideConfiguration; 61 62 /** 63 * Contains full configuration applied to this configuration container. Corresponds to full 64 * parent's config with applied {@link #mOverrideConfiguration}. 65 */ 66 private Configuration mFullConfiguration = new Configuration(); 67 68 /** 69 * Contains merged override configuration settings from the top of the hierarchy down to this 70 * particular instance. It is different from {@link #mFullConfiguration} because it starts from 71 * topmost container's override config instead of global config. 72 */ 73 private Configuration mMergedOverrideConfiguration = new Configuration(); 74 75 private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>(); 76 77 // TODO: Can't have ag/2592611 soon enough! 78 private final Configuration mTmpConfig = new Configuration(); 79 80 // Used for setting bounds 81 private final Rect mTmpRect = new Rect(); 82 83 static final int BOUNDS_CHANGE_NONE = 0; 84 // Return value from {@link setBounds} indicating the position of the override bounds changed. 85 static final int BOUNDS_CHANGE_POSITION = 1; 86 // Return value from {@link setBounds} indicating the size of the override bounds changed. 87 static final int BOUNDS_CHANGE_SIZE = 1 << 1; 88 89 90 /** 91 * Returns full configuration applied to this configuration container. 92 * This method should be used for getting settings applied in each particular level of the 93 * hierarchy. 94 */ getConfiguration()95 public Configuration getConfiguration() { 96 return mFullConfiguration; 97 } 98 99 /** 100 * Notify that parent config changed and we need to update full configuration. 101 * @see #mFullConfiguration 102 */ onConfigurationChanged(Configuration newParentConfig)103 public void onConfigurationChanged(Configuration newParentConfig) { 104 mFullConfiguration.setTo(newParentConfig); 105 mFullConfiguration.updateFrom(mOverrideConfiguration); 106 for (int i = getChildCount() - 1; i >= 0; --i) { 107 final ConfigurationContainer child = getChildAt(i); 108 child.onConfigurationChanged(mFullConfiguration); 109 } 110 } 111 112 /** Returns override configuration applied to this configuration container. */ getOverrideConfiguration()113 public Configuration getOverrideConfiguration() { 114 return mOverrideConfiguration; 115 } 116 117 /** 118 * Update override configuration and recalculate full config. 119 * @see #mOverrideConfiguration 120 * @see #mFullConfiguration 121 */ onOverrideConfigurationChanged(Configuration overrideConfiguration)122 public void onOverrideConfigurationChanged(Configuration overrideConfiguration) { 123 // Pre-compute this here, so we don't need to go through the entire Configuration when 124 // writing to proto (which has significant cost if we write a lot of empty configurations). 125 mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); 126 mOverrideConfiguration.setTo(overrideConfiguration); 127 // Update full configuration of this container and all its children. 128 final ConfigurationContainer parent = getParent(); 129 onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY); 130 // Update merged override config of this container and all its children. 131 onMergedOverrideConfigurationChanged(); 132 133 // Use the updated override configuration to notify listeners. 134 mTmpConfig.setTo(mOverrideConfiguration); 135 // Inform listeners of the change. 136 for (int i = mChangeListeners.size() - 1; i >=0; --i) { 137 mChangeListeners.get(i).onOverrideConfigurationChanged(mTmpConfig); 138 } 139 } 140 141 /** 142 * Get merged override configuration from the top of the hierarchy down to this particular 143 * instance. This should be reported to client as override config. 144 */ getMergedOverrideConfiguration()145 public Configuration getMergedOverrideConfiguration() { 146 return mMergedOverrideConfiguration; 147 } 148 149 /** 150 * Update merged override configuration based on corresponding parent's config and notify all 151 * its children. If there is no parent, merged override configuration will set equal to current 152 * override config. 153 * @see #mMergedOverrideConfiguration 154 */ onMergedOverrideConfigurationChanged()155 void onMergedOverrideConfigurationChanged() { 156 final ConfigurationContainer parent = getParent(); 157 if (parent != null) { 158 mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration()); 159 mMergedOverrideConfiguration.updateFrom(mOverrideConfiguration); 160 } else { 161 mMergedOverrideConfiguration.setTo(mOverrideConfiguration); 162 } 163 for (int i = getChildCount() - 1; i >= 0; --i) { 164 final ConfigurationContainer child = getChildAt(i); 165 child.onMergedOverrideConfigurationChanged(); 166 } 167 } 168 169 /** 170 * Indicates whether this container has not specified any bounds different from its parent. In 171 * this case, it will inherit the bounds of the first ancestor which specifies a bounds. 172 * @return {@code true} if no explicit bounds have been set at this container level. 173 * {@code false} otherwise. 174 */ matchParentBounds()175 public boolean matchParentBounds() { 176 return getOverrideBounds().isEmpty(); 177 } 178 179 /** 180 * Returns whether the bounds specified is considered the same as the existing override bounds. 181 * This is either when the two bounds are equal or the override bounds is empty and the 182 * specified bounds is null. 183 * 184 * @return {@code true} if the bounds are equivalent, {@code false} otherwise 185 */ equivalentOverrideBounds(Rect bounds)186 public boolean equivalentOverrideBounds(Rect bounds) { 187 return equivalentBounds(getOverrideBounds(), bounds); 188 } 189 190 /** 191 * Returns whether the two bounds are equal to each other or are a combination of null or empty. 192 */ equivalentBounds(Rect bounds, Rect other)193 public static boolean equivalentBounds(Rect bounds, Rect other) { 194 return bounds == other 195 || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null))) 196 || (other != null && other.isEmpty() && bounds == null); 197 } 198 199 /** 200 * Returns the effective bounds of this container, inheriting the first non-empty bounds set in 201 * its ancestral hierarchy, including itself. 202 * @return 203 */ getBounds()204 public Rect getBounds() { 205 mReturnBounds.set(getConfiguration().windowConfiguration.getBounds()); 206 return mReturnBounds; 207 } 208 getBounds(Rect outBounds)209 public void getBounds(Rect outBounds) { 210 outBounds.set(getBounds()); 211 } 212 213 /** 214 * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is 215 * shared for all calls to this method and should not be modified. 216 */ getOverrideBounds()217 public Rect getOverrideBounds() { 218 mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds()); 219 220 return mReturnBounds; 221 } 222 223 /** 224 * Returns {@code true} if the {@link WindowConfiguration} in the override 225 * {@link Configuration} specifies bounds. 226 */ hasOverrideBounds()227 public boolean hasOverrideBounds() { 228 return !getOverrideBounds().isEmpty(); 229 } 230 231 /** 232 * Sets the passed in {@link Rect} to the current bounds. 233 * @see {@link #getOverrideBounds()}. 234 */ getOverrideBounds(Rect outBounds)235 public void getOverrideBounds(Rect outBounds) { 236 outBounds.set(getOverrideBounds()); 237 } 238 239 /** 240 * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor. 241 * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If 242 * an empty {@link Rect} or null is specified, this container will be considered to match its 243 * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent. 244 * @param bounds The bounds defining the container size. 245 * @return a bitmask representing the types of changes made to the bounds. 246 */ setBounds(Rect bounds)247 public int setBounds(Rect bounds) { 248 int boundsChange = diffOverrideBounds(bounds); 249 250 if (boundsChange == BOUNDS_CHANGE_NONE) { 251 return boundsChange; 252 } 253 254 255 mTmpConfig.setTo(getOverrideConfiguration()); 256 mTmpConfig.windowConfiguration.setBounds(bounds); 257 onOverrideConfigurationChanged(mTmpConfig); 258 259 return boundsChange; 260 } 261 setBounds(int left, int top, int right, int bottom)262 public int setBounds(int left, int top, int right, int bottom) { 263 mTmpRect.set(left, top, right, bottom); 264 return setBounds(mTmpRect); 265 } 266 diffOverrideBounds(Rect bounds)267 int diffOverrideBounds(Rect bounds) { 268 if (equivalentOverrideBounds(bounds)) { 269 return BOUNDS_CHANGE_NONE; 270 } 271 272 int boundsChange = BOUNDS_CHANGE_NONE; 273 274 final Rect existingBounds = getOverrideBounds(); 275 276 if (bounds == null || existingBounds.left != bounds.left 277 || existingBounds.top != bounds.top) { 278 boundsChange |= BOUNDS_CHANGE_POSITION; 279 } 280 281 if (bounds == null || existingBounds.width() != bounds.width() 282 || existingBounds.height() != bounds.height()) { 283 boundsChange |= BOUNDS_CHANGE_SIZE; 284 } 285 286 return boundsChange; 287 } 288 getWindowConfiguration()289 public WindowConfiguration getWindowConfiguration() { 290 return mFullConfiguration.windowConfiguration; 291 } 292 293 /** Returns the windowing mode the configuration container is currently in. */ getWindowingMode()294 public int getWindowingMode() { 295 return mFullConfiguration.windowConfiguration.getWindowingMode(); 296 } 297 298 /** Sets the windowing mode for the configuration container. */ setWindowingMode( int windowingMode)299 public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { 300 mTmpConfig.setTo(getOverrideConfiguration()); 301 mTmpConfig.windowConfiguration.setWindowingMode(windowingMode); 302 onOverrideConfigurationChanged(mTmpConfig); 303 } 304 305 /** 306 * Returns true if this container is currently in multi-window mode. I.e. sharing the screen 307 * with another activity. 308 */ inMultiWindowMode()309 public boolean inMultiWindowMode() { 310 /*@WindowConfiguration.WindowingMode*/ int windowingMode = 311 mFullConfiguration.windowConfiguration.getWindowingMode(); 312 return windowingMode != WINDOWING_MODE_FULLSCREEN 313 && windowingMode != WINDOWING_MODE_UNDEFINED; 314 } 315 316 /** Returns true if this container is currently in split-screen windowing mode. */ inSplitScreenWindowingMode()317 public boolean inSplitScreenWindowingMode() { 318 /*@WindowConfiguration.WindowingMode*/ int windowingMode = 319 mFullConfiguration.windowConfiguration.getWindowingMode(); 320 321 return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY 322 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 323 } 324 325 /** Returns true if this container is currently in split-screen secondary windowing mode. */ inSplitScreenSecondaryWindowingMode()326 public boolean inSplitScreenSecondaryWindowingMode() { 327 /*@WindowConfiguration.WindowingMode*/ int windowingMode = 328 mFullConfiguration.windowConfiguration.getWindowingMode(); 329 330 return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; 331 } 332 inSplitScreenPrimaryWindowingMode()333 public boolean inSplitScreenPrimaryWindowingMode() { 334 return mFullConfiguration.windowConfiguration.getWindowingMode() 335 == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 336 } 337 338 /** 339 * Returns true if this container can be put in either 340 * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or 341 * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on 342 * its current state. 343 */ supportsSplitScreenWindowingMode()344 public boolean supportsSplitScreenWindowingMode() { 345 return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode(); 346 } 347 inPinnedWindowingMode()348 public boolean inPinnedWindowingMode() { 349 return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; 350 } 351 inFreeformWindowingMode()352 public boolean inFreeformWindowingMode() { 353 return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; 354 } 355 356 /** Returns the activity type associated with the the configuration container. */ 357 /*@WindowConfiguration.ActivityType*/ getActivityType()358 public int getActivityType() { 359 return mFullConfiguration.windowConfiguration.getActivityType(); 360 } 361 362 /** Sets the activity type to associate with the configuration container. */ setActivityType( int activityType)363 public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) { 364 int currentActivityType = getActivityType(); 365 if (currentActivityType == activityType) { 366 return; 367 } 368 if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) { 369 throw new IllegalStateException("Can't change activity type once set: " + this 370 + " activityType=" + activityTypeToString(activityType)); 371 } 372 mTmpConfig.setTo(getOverrideConfiguration()); 373 mTmpConfig.windowConfiguration.setActivityType(activityType); 374 onOverrideConfigurationChanged(mTmpConfig); 375 } 376 isActivityTypeHome()377 public boolean isActivityTypeHome() { 378 return getActivityType() == ACTIVITY_TYPE_HOME; 379 } 380 isActivityTypeRecents()381 public boolean isActivityTypeRecents() { 382 return getActivityType() == ACTIVITY_TYPE_RECENTS; 383 } 384 isActivityTypeAssistant()385 public boolean isActivityTypeAssistant() { 386 return getActivityType() == ACTIVITY_TYPE_ASSISTANT; 387 } 388 isActivityTypeStandard()389 public boolean isActivityTypeStandard() { 390 return getActivityType() == ACTIVITY_TYPE_STANDARD; 391 } 392 isActivityTypeStandardOrUndefined()393 public boolean isActivityTypeStandardOrUndefined() { 394 /*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType(); 395 return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED; 396 } 397 hasCompatibleActivityType(ConfigurationContainer other)398 public boolean hasCompatibleActivityType(ConfigurationContainer other) { 399 /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType(); 400 /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType(); 401 402 if (thisType == otherType) { 403 return true; 404 } 405 if (thisType == ACTIVITY_TYPE_ASSISTANT) { 406 // Assistant activities are only compatible with themselves... 407 return false; 408 } 409 // Otherwise we are compatible if us or other is not currently defined. 410 return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED; 411 } 412 413 /** 414 * Returns true if this container is compatible with the input windowing mode and activity type. 415 * The container is compatible: 416 * - If {@param activityType} and {@param windowingMode} match this container activity type and 417 * windowing mode. 418 * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or 419 * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also 420 * standard or undefined and its windowing mode matches {@param windowingMode}. 421 * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or 422 * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't 423 * also standard or undefined and its activity type matches {@param activityType} regardless of 424 * if {@param windowingMode} matches the containers windowing mode. 425 */ isCompatible(int windowingMode, int activityType)426 public boolean isCompatible(int windowingMode, int activityType) { 427 final int thisActivityType = getActivityType(); 428 final int thisWindowingMode = getWindowingMode(); 429 final boolean sameActivityType = thisActivityType == activityType; 430 final boolean sameWindowingMode = thisWindowingMode == windowingMode; 431 432 if (sameActivityType && sameWindowingMode) { 433 return true; 434 } 435 436 if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD) 437 || !isActivityTypeStandardOrUndefined()) { 438 // Only activity type need to match for non-standard activity types that are defined. 439 return sameActivityType; 440 } 441 442 // Otherwise we are compatible if the windowing mode is the same. 443 return sameWindowingMode; 444 } 445 registerConfigurationChangeListener(ConfigurationContainerListener listener)446 public void registerConfigurationChangeListener(ConfigurationContainerListener listener) { 447 if (mChangeListeners.contains(listener)) { 448 return; 449 } 450 mChangeListeners.add(listener); 451 listener.onOverrideConfigurationChanged(mOverrideConfiguration); 452 } 453 unregisterConfigurationChangeListener(ConfigurationContainerListener listener)454 public void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) { 455 mChangeListeners.remove(listener); 456 } 457 458 /** 459 * Must be called when new parent for the container was set. 460 */ onParentChanged()461 protected void onParentChanged() { 462 final ConfigurationContainer parent = getParent(); 463 // Removing parent usually means that we've detached this entity to destroy it or to attach 464 // to another parent. In both cases we don't need to update the configuration now. 465 if (parent != null) { 466 // Update full configuration of this container and all its children. 467 onConfigurationChanged(parent.mFullConfiguration); 468 // Update merged override configuration of this container and all its children. 469 onMergedOverrideConfigurationChanged(); 470 } 471 } 472 473 /** 474 * Write to a protocol buffer output stream. Protocol buffer message definition is at 475 * {@link com.android.server.wm.ConfigurationContainerProto}. 476 * 477 * @param proto Stream to write the ConfigurationContainer object to. 478 * @param fieldId Field Id of the ConfigurationContainer as defined in the parent 479 * message. 480 * @param trim If true, reduce amount of data written. 481 * @hide 482 */ 483 @CallSuper writeToProto(ProtoOutputStream proto, long fieldId, boolean trim)484 public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) { 485 final long token = proto.start(fieldId); 486 if (!trim || mHasOverrideConfiguration) { 487 mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION); 488 } 489 if (!trim) { 490 mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION); 491 mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION); 492 } 493 proto.end(token); 494 } 495 496 /** 497 * Dumps the names of this container children in the input print writer indenting each 498 * level with the input prefix. 499 */ dumpChildrenNames(PrintWriter pw, String prefix)500 public void dumpChildrenNames(PrintWriter pw, String prefix) { 501 final String childPrefix = prefix + " "; 502 pw.println(getName() 503 + " type=" + activityTypeToString(getActivityType()) 504 + " mode=" + windowingModeToString(getWindowingMode())); 505 for (int i = getChildCount() - 1; i >= 0; --i) { 506 final E cc = getChildAt(i); 507 pw.print(childPrefix + "#" + i + " "); 508 cc.dumpChildrenNames(pw, childPrefix); 509 } 510 } 511 getName()512 String getName() { 513 return toString(); 514 } 515 isAlwaysOnTop()516 boolean isAlwaysOnTop() { 517 return mFullConfiguration.windowConfiguration.isAlwaysOnTop(); 518 } 519 getChildCount()520 abstract protected int getChildCount(); 521 getChildAt(int index)522 abstract protected E getChildAt(int index); 523 getParent()524 abstract protected ConfigurationContainer getParent(); 525 } 526