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