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