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