• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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