1 /* 2 * Copyright (C) 2020 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.os.UserHandle.USER_SYSTEM; 20 import static android.view.Display.TYPE_VIRTUAL; 21 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 22 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 23 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; 24 25 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 26 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 27 28 import android.annotation.IntDef; 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.annotation.UserIdInt; 32 import android.app.WindowConfiguration; 33 import android.os.Environment; 34 import android.util.ArrayMap; 35 import android.util.ArraySet; 36 import android.util.AtomicFile; 37 import android.util.Slog; 38 import android.util.Xml; 39 import android.view.DisplayAddress; 40 import android.view.DisplayInfo; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.util.XmlUtils; 44 import com.android.modules.utils.TypedXmlPullParser; 45 import com.android.modules.utils.TypedXmlSerializer; 46 import com.android.server.wm.DisplayWindowSettings.SettingsProvider; 47 48 import org.xmlpull.v1.XmlPullParser; 49 import org.xmlpull.v1.XmlPullParserException; 50 51 import java.io.File; 52 import java.io.FileNotFoundException; 53 import java.io.FileOutputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.OutputStream; 57 import java.util.List; 58 import java.util.Map; 59 import java.util.Set; 60 import java.util.function.Consumer; 61 62 /** 63 * Implementation of {@link SettingsProvider} that reads the base settings provided in a display 64 * settings file stored in /vendor/etc and then overlays those values with the settings provided in 65 * /data/system. 66 * 67 * @see DisplayWindowSettings 68 */ 69 class DisplayWindowSettingsProvider implements SettingsProvider { 70 private static final String TAG = TAG_WITH_CLASS_NAME 71 ? "DisplayWindowSettingsProvider" : TAG_WM; 72 73 private static final String DATA_DISPLAY_SETTINGS_FILE_PATH = "system/display_settings.xml"; 74 private static final String VENDOR_DISPLAY_SETTINGS_FILE_PATH = "etc/display_settings.xml"; 75 private static final String WM_DISPLAY_COMMIT_TAG = "wm-displays"; 76 77 private static final int IDENTIFIER_UNIQUE_ID = 0; 78 private static final int IDENTIFIER_PORT = 1; 79 @IntDef(prefix = { "IDENTIFIER_" }, value = { 80 IDENTIFIER_UNIQUE_ID, 81 IDENTIFIER_PORT, 82 }) 83 @interface DisplayIdentifierType {} 84 85 /** Interface that allows reading the display window settings. */ 86 interface ReadableSettingsStorage { openRead()87 InputStream openRead() throws IOException; 88 } 89 90 /** Interface that allows reading and writing the display window settings. */ 91 interface WritableSettingsStorage extends ReadableSettingsStorage { startWrite()92 OutputStream startWrite() throws IOException; finishWrite(OutputStream os, boolean success)93 void finishWrite(OutputStream os, boolean success); 94 } 95 96 @NonNull 97 private ReadableSettings mBaseSettings; 98 @NonNull 99 private WritableSettings mOverrideSettings; 100 DisplayWindowSettingsProvider()101 DisplayWindowSettingsProvider() { 102 this(new AtomicFileStorage(getVendorSettingsFile()), 103 new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM))); 104 } 105 106 @VisibleForTesting DisplayWindowSettingsProvider(@onNull ReadableSettingsStorage baseSettingsStorage, @NonNull WritableSettingsStorage overrideSettingsStorage)107 DisplayWindowSettingsProvider(@NonNull ReadableSettingsStorage baseSettingsStorage, 108 @NonNull WritableSettingsStorage overrideSettingsStorage) { 109 mBaseSettings = new ReadableSettings(baseSettingsStorage); 110 mOverrideSettings = new WritableSettings(overrideSettingsStorage); 111 } 112 113 /** 114 * Overrides the path for the file that should be used to read base settings. If {@code null} is 115 * passed the default base settings file path will be used. 116 * 117 * @see #VENDOR_DISPLAY_SETTINGS_FILE_PATH 118 */ setBaseSettingsFilePath(@ullable String path)119 void setBaseSettingsFilePath(@Nullable String path) { 120 AtomicFile settingsFile; 121 File file = path != null ? new File(path) : null; 122 if (file != null && file.exists()) { 123 settingsFile = new AtomicFile(file, WM_DISPLAY_COMMIT_TAG); 124 } else { 125 Slog.w(TAG, "display settings " + path + " does not exist, using vendor defaults"); 126 settingsFile = getVendorSettingsFile(); 127 } 128 setBaseSettingsStorage(new AtomicFileStorage(settingsFile)); 129 } 130 131 /** 132 * Overrides the storage that should be used to read base settings. 133 * 134 * @see #setBaseSettingsFilePath(String) 135 */ 136 @VisibleForTesting setBaseSettingsStorage(@onNull ReadableSettingsStorage baseSettingsStorage)137 void setBaseSettingsStorage(@NonNull ReadableSettingsStorage baseSettingsStorage) { 138 mBaseSettings = new ReadableSettings(baseSettingsStorage); 139 } 140 141 /** 142 * Overrides the storage that should be used to save override settings for a user. 143 * 144 * @see #DATA_DISPLAY_SETTINGS_FILE_PATH 145 */ setOverrideSettingsForUser(@serIdInt int userId)146 void setOverrideSettingsForUser(@UserIdInt int userId) { 147 final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId); 148 setOverrideSettingsStorage(new AtomicFileStorage(settingsFile)); 149 } 150 151 /** 152 * Removes display override settings that are no longer associated with active displays. 153 * <p> 154 * This cleanup process is essential due to the dynamic nature of displays, which can 155 * be added or removed during various system events such as user switching or 156 * system server restarts. 157 * 158 * @param wms the WindowManagerService instance for retrieving all possible {@link DisplayInfo} 159 * for the given logical display. 160 * @param root the root window container used to obtain the currently active displays. 161 */ removeStaleDisplaySettingsLocked(@onNull WindowManagerService wms, @NonNull RootWindowContainer root)162 void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms, 163 @NonNull RootWindowContainer root) { 164 final Set<String> displayIdentifiers = new ArraySet<>(); 165 final Consumer<DisplayInfo> addDisplayIdentifier = 166 displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo)); 167 root.forAllDisplays(dc -> { 168 // Begin with the current display's information. Note that the display layout of the 169 // current device state might not include this display (e.g., external or virtual 170 // displays), resulting in empty possible display info. 171 addDisplayIdentifier.accept(dc.getDisplayInfo()); 172 173 // Then, add all possible display information for this display if available. 174 final List<DisplayInfo> displayInfos = wms.getPossibleDisplayInfoLocked(dc.mDisplayId); 175 final int size = displayInfos.size(); 176 for (int i = 0; i < size; i++) { 177 addDisplayIdentifier.accept(displayInfos.get(i)); 178 } 179 }); 180 mOverrideSettings.removeStaleDisplaySettings(displayIdentifiers); 181 } 182 183 /** 184 * Overrides the storage that should be used to save override settings. 185 * 186 * @see #setOverrideSettingsForUser(int) 187 */ 188 @VisibleForTesting setOverrideSettingsStorage(@onNull WritableSettingsStorage overrideSettingsStorage)189 void setOverrideSettingsStorage(@NonNull WritableSettingsStorage overrideSettingsStorage) { 190 mOverrideSettings = new WritableSettings(overrideSettingsStorage); 191 } 192 193 @Override 194 @NonNull getSettings(@onNull DisplayInfo info)195 public SettingsEntry getSettings(@NonNull DisplayInfo info) { 196 SettingsEntry baseSettings = mBaseSettings.getSettingsEntry(info); 197 SettingsEntry overrideSettings = mOverrideSettings.getOrCreateSettingsEntry(info); 198 if (baseSettings == null) { 199 return new SettingsEntry(overrideSettings); 200 } else { 201 SettingsEntry mergedSettings = new SettingsEntry(baseSettings); 202 mergedSettings.updateFrom(overrideSettings); 203 return mergedSettings; 204 } 205 } 206 207 @Override 208 @NonNull getOverrideSettings(@onNull DisplayInfo info)209 public SettingsEntry getOverrideSettings(@NonNull DisplayInfo info) { 210 return new SettingsEntry(mOverrideSettings.getOrCreateSettingsEntry(info)); 211 } 212 213 @Override updateOverrideSettings(@onNull DisplayInfo info, @NonNull SettingsEntry overrides)214 public void updateOverrideSettings(@NonNull DisplayInfo info, 215 @NonNull SettingsEntry overrides) { 216 mOverrideSettings.updateSettingsEntry(info, overrides); 217 } 218 219 @Override onDisplayRemoved(@onNull DisplayInfo info)220 public void onDisplayRemoved(@NonNull DisplayInfo info) { 221 mOverrideSettings.onDisplayRemoved(info); 222 } 223 224 @Override clearDisplaySettings(@onNull DisplayInfo info)225 public void clearDisplaySettings(@NonNull DisplayInfo info) { 226 mOverrideSettings.clearDisplaySettings(info); 227 } 228 229 @VisibleForTesting getOverrideSettingsSize()230 int getOverrideSettingsSize() { 231 return mOverrideSettings.mSettings.size(); 232 } 233 234 /** 235 * Class that allows reading {@link SettingsEntry entries} from a 236 * {@link ReadableSettingsStorage}. 237 */ 238 private static class ReadableSettings { 239 /** 240 * The preferred type of a display identifier to use when storing and retrieving entries 241 * from the settings entries. 242 * 243 * @see #getIdentifier(DisplayInfo) 244 */ 245 @DisplayIdentifierType 246 protected int mIdentifierType; 247 @NonNull 248 protected final ArrayMap<String, SettingsEntry> mSettings = new ArrayMap<>(); 249 ReadableSettings(@onNull ReadableSettingsStorage settingsStorage)250 ReadableSettings(@NonNull ReadableSettingsStorage settingsStorage) { 251 loadSettings(settingsStorage); 252 } 253 254 @Nullable getSettingsEntry(@onNull DisplayInfo info)255 final SettingsEntry getSettingsEntry(@NonNull DisplayInfo info) { 256 final String identifier = getIdentifier(info); 257 SettingsEntry settings; 258 // Try to get corresponding settings using preferred identifier for the current config. 259 if ((settings = mSettings.get(identifier)) != null) { 260 return settings; 261 } 262 // Else, fall back to the display name. 263 if ((settings = mSettings.get(info.name)) != null) { 264 // Found an entry stored with old identifier. 265 mSettings.remove(info.name); 266 mSettings.put(identifier, settings); 267 return settings; 268 } 269 return null; 270 } 271 272 /** Gets the identifier of choice for the current config. */ 273 @NonNull getIdentifier(@onNull DisplayInfo displayInfo)274 protected final String getIdentifier(@NonNull DisplayInfo displayInfo) { 275 if (mIdentifierType == IDENTIFIER_PORT && displayInfo.address != null) { 276 // Config suggests using port as identifier for physical displays. 277 if (displayInfo.address instanceof DisplayAddress.Physical) { 278 return "port:" + ((DisplayAddress.Physical) displayInfo.address).getPort(); 279 } 280 } 281 return displayInfo.uniqueId; 282 } 283 loadSettings(@onNull ReadableSettingsStorage settingsStorage)284 private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) { 285 FileData fileData = readSettings(settingsStorage); 286 if (fileData != null) { 287 mIdentifierType = fileData.mIdentifierType; 288 mSettings.putAll(fileData.mSettings); 289 } 290 } 291 } 292 293 /** 294 * Class that allows reading {@link SettingsEntry entries} from, and writing entries to, a 295 * {@link WritableSettingsStorage}. 296 */ 297 private static final class WritableSettings extends ReadableSettings { 298 @NonNull 299 private final WritableSettingsStorage mSettingsStorage; 300 @NonNull 301 private final ArraySet<String> mVirtualDisplayIdentifiers = new ArraySet<>(); 302 WritableSettings(@onNull WritableSettingsStorage settingsStorage)303 WritableSettings(@NonNull WritableSettingsStorage settingsStorage) { 304 super(settingsStorage); 305 mSettingsStorage = settingsStorage; 306 } 307 308 @NonNull getOrCreateSettingsEntry(@onNull DisplayInfo info)309 SettingsEntry getOrCreateSettingsEntry(@NonNull DisplayInfo info) { 310 final String identifier = getIdentifier(info); 311 SettingsEntry settings; 312 // Try to get corresponding settings using preferred identifier for the current config. 313 if ((settings = mSettings.get(identifier)) != null) { 314 return settings; 315 } 316 // Else, fall back to the display name. 317 if ((settings = mSettings.get(info.name)) != null) { 318 // Found an entry stored with old identifier. 319 mSettings.remove(info.name); 320 mSettings.put(identifier, settings); 321 writeSettings(); 322 return settings; 323 } 324 325 settings = new SettingsEntry(); 326 mSettings.put(identifier, settings); 327 if (info.type == TYPE_VIRTUAL) { 328 // Keep track of virtual display. We don't want to write virtual display settings to 329 // file. 330 mVirtualDisplayIdentifiers.add(identifier); 331 } 332 return settings; 333 } 334 updateSettingsEntry(@onNull DisplayInfo info, @NonNull SettingsEntry settings)335 void updateSettingsEntry(@NonNull DisplayInfo info, @NonNull SettingsEntry settings) { 336 final SettingsEntry overrideSettings = getOrCreateSettingsEntry(info); 337 final boolean changed = overrideSettings.setTo(settings); 338 if (changed && info.type != TYPE_VIRTUAL) { 339 writeSettings(); 340 } 341 } 342 onDisplayRemoved(@onNull DisplayInfo info)343 void onDisplayRemoved(@NonNull DisplayInfo info) { 344 final String identifier = getIdentifier(info); 345 if (!mSettings.containsKey(identifier)) { 346 return; 347 } 348 if (mVirtualDisplayIdentifiers.remove(identifier) 349 || mSettings.get(identifier).isEmpty()) { 350 // Don't keep track of virtual display or empty settings to avoid growing the cached 351 // map. 352 mSettings.remove(identifier); 353 } 354 } 355 clearDisplaySettings(@onNull DisplayInfo info)356 void clearDisplaySettings(@NonNull DisplayInfo info) { 357 final String identifier = getIdentifier(info); 358 mSettings.remove(identifier); 359 mVirtualDisplayIdentifiers.remove(identifier); 360 } 361 removeStaleDisplaySettings(@onNull Set<String> currentDisplayIdentifiers)362 void removeStaleDisplaySettings(@NonNull Set<String> currentDisplayIdentifiers) { 363 if (mSettings.retainAll(currentDisplayIdentifiers)) { 364 writeSettings(); 365 } 366 } 367 writeSettings()368 private void writeSettings() { 369 final FileData fileData = new FileData(); 370 fileData.mIdentifierType = mIdentifierType; 371 final int size = mSettings.size(); 372 for (int i = 0; i < size; i++) { 373 final String identifier = mSettings.keyAt(i); 374 if (mVirtualDisplayIdentifiers.contains(identifier)) { 375 // Do not write virtual display settings to file. 376 continue; 377 } 378 fileData.mSettings.put(identifier, mSettings.get(identifier)); 379 } 380 DisplayWindowSettingsProvider.writeSettings(mSettingsStorage, fileData); 381 } 382 } 383 384 @NonNull getVendorSettingsFile()385 private static AtomicFile getVendorSettingsFile() { 386 // First look under product path for treblized builds. 387 File vendorFile = new File(Environment.getProductDirectory(), 388 VENDOR_DISPLAY_SETTINGS_FILE_PATH); 389 if (!vendorFile.exists()) { 390 // Try and look in vendor path. 391 vendorFile = new File(Environment.getVendorDirectory(), 392 VENDOR_DISPLAY_SETTINGS_FILE_PATH); 393 } 394 return new AtomicFile(vendorFile, WM_DISPLAY_COMMIT_TAG); 395 } 396 397 @NonNull getOverrideSettingsFileForUser(@serIdInt int userId)398 private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { 399 final File directory = (userId == USER_SYSTEM) 400 ? Environment.getDataDirectory() 401 : Environment.getDataSystemCeDirectory(userId); 402 final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH); 403 return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG); 404 } 405 406 @Nullable readSettings(@onNull ReadableSettingsStorage storage)407 private static FileData readSettings(@NonNull ReadableSettingsStorage storage) { 408 InputStream stream; 409 try { 410 stream = storage.openRead(); 411 } catch (IOException e) { 412 Slog.i(TAG, "No existing display settings, starting empty"); 413 return null; 414 } 415 FileData fileData = new FileData(); 416 boolean success = false; 417 try { 418 TypedXmlPullParser parser = Xml.resolvePullParser(stream); 419 int type; 420 while ((type = parser.next()) != XmlPullParser.START_TAG 421 && type != XmlPullParser.END_DOCUMENT) { 422 // Do nothing. 423 } 424 425 if (type != XmlPullParser.START_TAG) { 426 throw new IllegalStateException("no start tag found"); 427 } 428 429 int outerDepth = parser.getDepth(); 430 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 431 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 432 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 433 continue; 434 } 435 436 String tagName = parser.getName(); 437 if (tagName.equals("display")) { 438 readDisplay(parser, fileData); 439 } else if (tagName.equals("config")) { 440 readConfig(parser, fileData); 441 } else { 442 Slog.w(TAG, "Unknown element under <display-settings>: " 443 + parser.getName()); 444 XmlUtils.skipCurrentTag(parser); 445 } 446 } 447 success = true; 448 } catch (IllegalStateException e) { 449 Slog.w(TAG, "Failed parsing " + e); 450 } catch (NullPointerException e) { 451 Slog.w(TAG, "Failed parsing " + e); 452 } catch (NumberFormatException e) { 453 Slog.w(TAG, "Failed parsing " + e); 454 } catch (XmlPullParserException e) { 455 Slog.w(TAG, "Failed parsing " + e); 456 } catch (IOException e) { 457 Slog.w(TAG, "Failed parsing " + e); 458 } catch (IndexOutOfBoundsException e) { 459 Slog.w(TAG, "Failed parsing " + e); 460 } finally { 461 try { 462 stream.close(); 463 } catch (IOException ignored) { 464 } 465 } 466 if (!success) { 467 fileData.mSettings.clear(); 468 } 469 return fileData; 470 } 471 getIntAttribute(@onNull TypedXmlPullParser parser, @NonNull String name, int defaultValue)472 private static int getIntAttribute(@NonNull TypedXmlPullParser parser, @NonNull String name, 473 int defaultValue) { 474 return parser.getAttributeInt(null, name, defaultValue); 475 } 476 477 @Nullable getIntegerAttribute(@onNull TypedXmlPullParser parser, @NonNull String name, @Nullable Integer defaultValue)478 private static Integer getIntegerAttribute(@NonNull TypedXmlPullParser parser, 479 @NonNull String name, @Nullable Integer defaultValue) { 480 try { 481 return parser.getAttributeInt(null, name); 482 } catch (Exception ignored) { 483 return defaultValue; 484 } 485 } 486 487 @Nullable getBooleanAttribute(@onNull TypedXmlPullParser parser, @NonNull String name, @Nullable Boolean defaultValue)488 private static Boolean getBooleanAttribute(@NonNull TypedXmlPullParser parser, 489 @NonNull String name, @Nullable Boolean defaultValue) { 490 try { 491 return parser.getAttributeBoolean(null, name); 492 } catch (Exception ignored) { 493 return defaultValue; 494 } 495 } 496 readDisplay(@onNull TypedXmlPullParser parser, @NonNull FileData fileData)497 private static void readDisplay(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData) 498 throws NumberFormatException, XmlPullParserException, IOException { 499 String name = parser.getAttributeValue(null, "name"); 500 if (name != null) { 501 SettingsEntry settingsEntry = new SettingsEntry(); 502 settingsEntry.mWindowingMode = getIntAttribute(parser, "windowingMode", 503 WindowConfiguration.WINDOWING_MODE_UNDEFINED /* defaultValue */); 504 settingsEntry.mUserRotationMode = getIntegerAttribute(parser, "userRotationMode", 505 null /* defaultValue */); 506 settingsEntry.mUserRotation = getIntegerAttribute(parser, "userRotation", 507 null /* defaultValue */); 508 settingsEntry.mForcedWidth = getIntAttribute(parser, "forcedWidth", 509 0 /* defaultValue */); 510 settingsEntry.mForcedHeight = getIntAttribute(parser, "forcedHeight", 511 0 /* defaultValue */); 512 settingsEntry.mForcedDensity = getIntAttribute(parser, "forcedDensity", 513 0 /* defaultValue */); 514 settingsEntry.mForcedDensityRatio = parser.getAttributeFloat(null, "forcedDensityRatio", 515 0.0f /* defaultValue */); 516 settingsEntry.mForcedScalingMode = getIntegerAttribute(parser, "forcedScalingMode", 517 null /* defaultValue */); 518 settingsEntry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode", 519 REMOVE_CONTENT_MODE_UNDEFINED /* defaultValue */); 520 settingsEntry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser, 521 "shouldShowWithInsecureKeyguard", null /* defaultValue */); 522 settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser, 523 "shouldShowSystemDecors", null /* defaultValue */); 524 final Boolean shouldShowIme = getBooleanAttribute(parser, "shouldShowIme", 525 null /* defaultValue */); 526 if (shouldShowIme != null) { 527 settingsEntry.mImePolicy = shouldShowIme ? DISPLAY_IME_POLICY_LOCAL 528 : DISPLAY_IME_POLICY_FALLBACK_DISPLAY; 529 } else { 530 settingsEntry.mImePolicy = getIntegerAttribute(parser, "imePolicy", 531 null /* defaultValue */); 532 } 533 settingsEntry.mFixedToUserRotation = getIntegerAttribute(parser, "fixedToUserRotation", 534 null /* defaultValue */); 535 settingsEntry.mIgnoreOrientationRequest = getBooleanAttribute(parser, 536 "ignoreOrientationRequest", null /* defaultValue */); 537 settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser, 538 "ignoreDisplayCutout", null /* defaultValue */); 539 settingsEntry.mDontMoveToTop = getBooleanAttribute(parser, 540 "dontMoveToTop", null /* defaultValue */); 541 542 fileData.mSettings.put(name, settingsEntry); 543 } 544 XmlUtils.skipCurrentTag(parser); 545 } 546 readConfig(@onNull TypedXmlPullParser parser, @NonNull FileData fileData)547 private static void readConfig(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData) 548 throws NumberFormatException, 549 XmlPullParserException, IOException { 550 fileData.mIdentifierType = getIntAttribute(parser, "identifier", 551 IDENTIFIER_UNIQUE_ID); 552 XmlUtils.skipCurrentTag(parser); 553 } 554 writeSettings(@onNull WritableSettingsStorage storage, @NonNull FileData data)555 private static void writeSettings(@NonNull WritableSettingsStorage storage, 556 @NonNull FileData data) { 557 OutputStream stream; 558 try { 559 stream = storage.startWrite(); 560 } catch (IOException e) { 561 Slog.w(TAG, "Failed to write display settings: " + e); 562 return; 563 } 564 565 boolean success = false; 566 try { 567 TypedXmlSerializer out = Xml.resolveSerializer(stream); 568 out.startDocument(null, true); 569 570 out.startTag(null, "display-settings"); 571 572 out.startTag(null, "config"); 573 out.attributeInt(null, "identifier", data.mIdentifierType); 574 out.endTag(null, "config"); 575 576 for (Map.Entry<String, SettingsEntry> entry 577 : data.mSettings.entrySet()) { 578 String displayIdentifier = entry.getKey(); 579 SettingsEntry settingsEntry = entry.getValue(); 580 if (settingsEntry.isEmpty()) { 581 continue; 582 } 583 584 out.startTag(null, "display"); 585 out.attribute(null, "name", displayIdentifier); 586 if (settingsEntry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) { 587 out.attributeInt(null, "windowingMode", settingsEntry.mWindowingMode); 588 } 589 if (settingsEntry.mUserRotationMode != null) { 590 out.attributeInt(null, "userRotationMode", 591 settingsEntry.mUserRotationMode); 592 } 593 if (settingsEntry.mUserRotation != null) { 594 out.attributeInt(null, "userRotation", 595 settingsEntry.mUserRotation); 596 } 597 if (settingsEntry.mForcedWidth != 0 && settingsEntry.mForcedHeight != 0) { 598 out.attributeInt(null, "forcedWidth", settingsEntry.mForcedWidth); 599 out.attributeInt(null, "forcedHeight", settingsEntry.mForcedHeight); 600 } 601 if (settingsEntry.mForcedDensity != 0) { 602 out.attributeInt(null, "forcedDensity", settingsEntry.mForcedDensity); 603 } 604 if (settingsEntry.mForcedDensityRatio != 0.0f) { 605 out.attributeFloat(null, "forcedDensityRatio", 606 settingsEntry.mForcedDensityRatio); 607 } 608 if (settingsEntry.mForcedScalingMode != null) { 609 out.attributeInt(null, "forcedScalingMode", 610 settingsEntry.mForcedScalingMode); 611 } 612 if (settingsEntry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) { 613 out.attributeInt(null, "removeContentMode", settingsEntry.mRemoveContentMode); 614 } 615 if (settingsEntry.mShouldShowWithInsecureKeyguard != null) { 616 out.attributeBoolean(null, "shouldShowWithInsecureKeyguard", 617 settingsEntry.mShouldShowWithInsecureKeyguard); 618 } 619 if (settingsEntry.mShouldShowSystemDecors != null) { 620 out.attributeBoolean(null, "shouldShowSystemDecors", 621 settingsEntry.mShouldShowSystemDecors); 622 } 623 if (settingsEntry.mImePolicy != null) { 624 out.attributeInt(null, "imePolicy", settingsEntry.mImePolicy); 625 } 626 if (settingsEntry.mFixedToUserRotation != null) { 627 out.attributeInt(null, "fixedToUserRotation", 628 settingsEntry.mFixedToUserRotation); 629 } 630 if (settingsEntry.mIgnoreOrientationRequest != null) { 631 out.attributeBoolean(null, "ignoreOrientationRequest", 632 settingsEntry.mIgnoreOrientationRequest); 633 } 634 if (settingsEntry.mIgnoreDisplayCutout != null) { 635 out.attributeBoolean(null, "ignoreDisplayCutout", 636 settingsEntry.mIgnoreDisplayCutout); 637 } 638 if (settingsEntry.mDontMoveToTop != null) { 639 out.attributeBoolean(null, "dontMoveToTop", 640 settingsEntry.mDontMoveToTop); 641 } 642 out.endTag(null, "display"); 643 } 644 645 out.endTag(null, "display-settings"); 646 out.endDocument(); 647 success = true; 648 } catch (IOException e) { 649 Slog.w(TAG, "Failed to write display window settings.", e); 650 } finally { 651 storage.finishWrite(stream, success); 652 } 653 } 654 655 private static final class FileData { 656 int mIdentifierType; 657 @NonNull 658 final Map<String, SettingsEntry> mSettings = new ArrayMap<>(); 659 660 @Override toString()661 public String toString() { 662 return "FileData{" 663 + "mIdentifierType=" + mIdentifierType 664 + ", mSettings=" + mSettings 665 + '}'; 666 } 667 } 668 669 private static final class AtomicFileStorage implements WritableSettingsStorage { 670 @NonNull 671 private final AtomicFile mAtomicFile; 672 AtomicFileStorage(@onNull AtomicFile atomicFile)673 AtomicFileStorage(@NonNull AtomicFile atomicFile) { 674 mAtomicFile = atomicFile; 675 } 676 677 @Override openRead()678 public InputStream openRead() throws FileNotFoundException { 679 return mAtomicFile.openRead(); 680 } 681 682 @Override startWrite()683 public OutputStream startWrite() throws IOException { 684 return mAtomicFile.startWrite(); 685 } 686 687 @Override finishWrite(OutputStream os, boolean success)688 public void finishWrite(OutputStream os, boolean success) { 689 if (!(os instanceof FileOutputStream)) { 690 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os); 691 } 692 FileOutputStream fos = (FileOutputStream) os; 693 if (success) { 694 mAtomicFile.finishWrite(fos); 695 } else { 696 mAtomicFile.failWrite(fos); 697 } 698 } 699 } 700 } 701