• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.REMOVE_CONTENT_MODE_DESTROY;
20 import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
21 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
22 
23 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO;
24 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
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.Nullable;
30 import android.app.WindowConfiguration;
31 import android.os.Environment;
32 import android.os.FileUtils;
33 import android.provider.Settings;
34 import android.util.AtomicFile;
35 import android.util.Slog;
36 import android.util.Xml;
37 import android.view.Display;
38 import android.view.DisplayAddress;
39 import android.view.DisplayInfo;
40 import android.view.IWindowManager;
41 import android.view.Surface;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.util.FastXmlSerializer;
45 import com.android.internal.util.XmlUtils;
46 import com.android.server.policy.WindowManagerPolicy;
47 import com.android.server.wm.DisplayContent.ForceScalingMode;
48 
49 import org.xmlpull.v1.XmlPullParser;
50 import org.xmlpull.v1.XmlPullParserException;
51 import org.xmlpull.v1.XmlSerializer;
52 
53 import java.io.File;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.nio.charset.StandardCharsets;
60 import java.util.HashMap;
61 
62 /**
63  * Current persistent settings about a display
64  */
65 class DisplayWindowSettings {
66     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayWindowSettings" : TAG_WM;
67 
68     private static final String SYSTEM_DIRECTORY = "system";
69     private static final String DISPLAY_SETTINGS_FILE_NAME = "display_settings.xml";
70     private static final String VENDOR_DISPLAY_SETTINGS_PATH = "etc/" + DISPLAY_SETTINGS_FILE_NAME;
71     private static final String WM_DISPLAY_COMMIT_TAG = "wm-displays";
72 
73     private static final int IDENTIFIER_UNIQUE_ID = 0;
74     private static final int IDENTIFIER_PORT = 1;
75     @IntDef(prefix = { "IDENTIFIER_" }, value = {
76             IDENTIFIER_UNIQUE_ID,
77             IDENTIFIER_PORT,
78     })
79     @interface DisplayIdentifierType {}
80 
81     private final WindowManagerService mService;
82     private final HashMap<String, Entry> mEntries = new HashMap<>();
83     private final SettingPersister mStorage;
84 
85     /**
86      * The preferred type of a display identifier to use when storing and retrieving entries.
87      * {@link #getIdentifier(DisplayInfo)} must be used to get current preferred identifier for each
88      * display. It will fall back to using {@link #IDENTIFIER_UNIQUE_ID} if the currently selected
89      * one is not applicable to a particular display.
90      */
91     @DisplayIdentifierType
92     private int mIdentifier = IDENTIFIER_UNIQUE_ID;
93 
94     /** Interface for persisting the display window settings. */
95     interface SettingPersister {
openRead()96         InputStream openRead() throws IOException;
startWrite()97         OutputStream startWrite() throws IOException;
finishWrite(OutputStream os, boolean success)98         void finishWrite(OutputStream os, boolean success);
99     }
100 
101     private static class Entry {
102         private final String mName;
103         private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
104         private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
105         private int mUserRotation = Surface.ROTATION_0;
106         private int mForcedWidth;
107         private int mForcedHeight;
108         private int mForcedDensity;
109         private int mForcedScalingMode = FORCE_SCALING_MODE_AUTO;
110         private int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED;
111         private boolean mShouldShowWithInsecureKeyguard = false;
112         private boolean mShouldShowSystemDecors = false;
113         private boolean mShouldShowIme = false;
114         private int mFixedToUserRotation = IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
115 
Entry(String name)116         private Entry(String name) {
117             mName = name;
118         }
119 
Entry(String name, Entry copyFrom)120         private Entry(String name, Entry copyFrom) {
121             this(name);
122             mWindowingMode = copyFrom.mWindowingMode;
123             mUserRotationMode = copyFrom.mUserRotationMode;
124             mUserRotation = copyFrom.mUserRotation;
125             mForcedWidth = copyFrom.mForcedWidth;
126             mForcedHeight = copyFrom.mForcedHeight;
127             mForcedDensity = copyFrom.mForcedDensity;
128             mForcedScalingMode = copyFrom.mForcedScalingMode;
129             mRemoveContentMode = copyFrom.mRemoveContentMode;
130             mShouldShowWithInsecureKeyguard = copyFrom.mShouldShowWithInsecureKeyguard;
131             mShouldShowSystemDecors = copyFrom.mShouldShowSystemDecors;
132             mShouldShowIme = copyFrom.mShouldShowIme;
133             mFixedToUserRotation = copyFrom.mFixedToUserRotation;
134         }
135 
136         /** @return {@code true} if all values are default. */
isEmpty()137         private boolean isEmpty() {
138             return mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED
139                     && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
140                     && mUserRotation == Surface.ROTATION_0
141                     && mForcedWidth == 0 && mForcedHeight == 0 && mForcedDensity == 0
142                     && mForcedScalingMode == FORCE_SCALING_MODE_AUTO
143                     && mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED
144                     && !mShouldShowWithInsecureKeyguard
145                     && !mShouldShowSystemDecors
146                     && !mShouldShowIme
147                     && mFixedToUserRotation == IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT;
148         }
149     }
150 
DisplayWindowSettings(WindowManagerService service)151     DisplayWindowSettings(WindowManagerService service) {
152         this(service, new AtomicFileStorage());
153     }
154 
155     @VisibleForTesting
DisplayWindowSettings(WindowManagerService service, SettingPersister storageImpl)156     DisplayWindowSettings(WindowManagerService service, SettingPersister storageImpl) {
157         mService = service;
158         mStorage = storageImpl;
159         readSettings();
160     }
161 
getEntry(DisplayInfo displayInfo)162     private @Nullable Entry getEntry(DisplayInfo displayInfo) {
163         final String identifier = getIdentifier(displayInfo);
164         Entry entry;
165         // Try to get corresponding entry using preferred identifier for the current config.
166         if ((entry = mEntries.get(identifier)) != null) {
167             return entry;
168         }
169         // Else, fall back to the display name.
170         if ((entry = mEntries.get(displayInfo.name)) != null) {
171             // Found an entry stored with old identifier - upgrade to the new type now.
172             return updateIdentifierForEntry(entry, displayInfo);
173         }
174         return null;
175     }
176 
getOrCreateEntry(DisplayInfo displayInfo)177     private Entry getOrCreateEntry(DisplayInfo displayInfo) {
178         final Entry entry = getEntry(displayInfo);
179         return entry != null ? entry : new Entry(getIdentifier(displayInfo));
180     }
181 
182     /**
183      * Upgrades the identifier of a legacy entry. Does it by copying the data from the old record
184      * and clearing the old key in memory. The entry will be written to storage next time when a
185      * setting changes.
186      */
updateIdentifierForEntry(Entry entry, DisplayInfo displayInfo)187     private Entry updateIdentifierForEntry(Entry entry, DisplayInfo displayInfo) {
188         final Entry newEntry = new Entry(getIdentifier(displayInfo), entry);
189         removeEntry(displayInfo);
190         mEntries.put(newEntry.mName, newEntry);
191         return newEntry;
192     }
193 
setUserRotation(DisplayContent displayContent, int rotationMode, int rotation)194     void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
195         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
196         final Entry entry = getOrCreateEntry(displayInfo);
197         entry.mUserRotationMode = rotationMode;
198         entry.mUserRotation = rotation;
199         writeSettingsIfNeeded(entry, displayInfo);
200     }
201 
setForcedSize(DisplayContent displayContent, int width, int height)202     void setForcedSize(DisplayContent displayContent, int width, int height) {
203         if (displayContent.isDefaultDisplay) {
204             final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
205             Settings.Global.putString(mService.mContext.getContentResolver(),
206                     Settings.Global.DISPLAY_SIZE_FORCED, sizeString);
207             return;
208         }
209 
210         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
211         final Entry entry = getOrCreateEntry(displayInfo);
212         entry.mForcedWidth = width;
213         entry.mForcedHeight = height;
214         writeSettingsIfNeeded(entry, displayInfo);
215     }
216 
setForcedDensity(DisplayContent displayContent, int density, int userId)217     void setForcedDensity(DisplayContent displayContent, int density, int userId) {
218         if (displayContent.isDefaultDisplay) {
219             final String densityString = density == 0 ? "" : Integer.toString(density);
220             Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
221                     Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
222             return;
223         }
224 
225         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
226         final Entry entry = getOrCreateEntry(displayInfo);
227         entry.mForcedDensity = density;
228         writeSettingsIfNeeded(entry, displayInfo);
229     }
230 
setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode)231     void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
232         if (displayContent.isDefaultDisplay) {
233             Settings.Global.putInt(mService.mContext.getContentResolver(),
234                     Settings.Global.DISPLAY_SCALING_FORCE, mode);
235             return;
236         }
237 
238         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
239         final Entry entry = getOrCreateEntry(displayInfo);
240         entry.mForcedScalingMode = mode;
241         writeSettingsIfNeeded(entry, displayInfo);
242     }
243 
setFixedToUserRotation(DisplayContent displayContent, int fixedToUserRotation)244     void setFixedToUserRotation(DisplayContent displayContent, int fixedToUserRotation) {
245         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
246         final Entry entry = getOrCreateEntry(displayInfo);
247         entry.mFixedToUserRotation = fixedToUserRotation;
248         writeSettingsIfNeeded(entry, displayInfo);
249     }
250 
getWindowingModeLocked(Entry entry, DisplayContent dc)251     private int getWindowingModeLocked(Entry entry, DisplayContent dc) {
252         int windowingMode = entry != null ? entry.mWindowingMode
253                 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
254         // This display used to be in freeform, but we don't support freeform anymore, so fall
255         // back to fullscreen.
256         if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
257                 && !mService.mAtmService.mSupportsFreeformWindowManagement) {
258             return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
259         }
260         // No record is present so use default windowing mode policy.
261         if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
262             windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
263                     && (mService.mIsPc || dc.forceDesktopMode())
264                     ? WindowConfiguration.WINDOWING_MODE_FREEFORM
265                     : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
266         }
267         return windowingMode;
268     }
269 
getWindowingModeLocked(DisplayContent dc)270     int getWindowingModeLocked(DisplayContent dc) {
271         final DisplayInfo displayInfo = dc.getDisplayInfo();
272         final Entry entry = getEntry(displayInfo);
273         return getWindowingModeLocked(entry, dc);
274     }
275 
setWindowingModeLocked(DisplayContent dc, int mode)276     void setWindowingModeLocked(DisplayContent dc, int mode) {
277         final DisplayInfo displayInfo = dc.getDisplayInfo();
278         final Entry entry = getOrCreateEntry(displayInfo);
279         entry.mWindowingMode = mode;
280         dc.setWindowingMode(mode);
281         writeSettingsIfNeeded(entry, displayInfo);
282     }
283 
getRemoveContentModeLocked(DisplayContent dc)284     int getRemoveContentModeLocked(DisplayContent dc) {
285         final DisplayInfo displayInfo = dc.getDisplayInfo();
286         final Entry entry = getEntry(displayInfo);
287         if (entry == null || entry.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
288             if (dc.isPrivate()) {
289                 // For private displays by default content is destroyed on removal.
290                 return REMOVE_CONTENT_MODE_DESTROY;
291             }
292             // For other displays by default content is moved to primary on removal.
293             return REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
294         }
295         return entry.mRemoveContentMode;
296     }
297 
setRemoveContentModeLocked(DisplayContent dc, int mode)298     void setRemoveContentModeLocked(DisplayContent dc, int mode) {
299         final DisplayInfo displayInfo = dc.getDisplayInfo();
300         final Entry entry = getOrCreateEntry(displayInfo);
301         entry.mRemoveContentMode = mode;
302         writeSettingsIfNeeded(entry, displayInfo);
303     }
304 
shouldShowWithInsecureKeyguardLocked(DisplayContent dc)305     boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
306         final DisplayInfo displayInfo = dc.getDisplayInfo();
307         final Entry entry = getEntry(displayInfo);
308         if (entry == null) {
309             return false;
310         }
311         return entry.mShouldShowWithInsecureKeyguard;
312     }
313 
setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow)314     void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
315         if (!dc.isPrivate() && shouldShow) {
316             Slog.e(TAG, "Public display can't be allowed to show content when locked");
317             return;
318         }
319 
320         final DisplayInfo displayInfo = dc.getDisplayInfo();
321         final Entry entry = getOrCreateEntry(displayInfo);
322         entry.mShouldShowWithInsecureKeyguard = shouldShow;
323         writeSettingsIfNeeded(entry, displayInfo);
324     }
325 
shouldShowSystemDecorsLocked(DisplayContent dc)326     boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
327         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
328             // For default display should show system decors.
329             return true;
330         }
331 
332         final DisplayInfo displayInfo = dc.getDisplayInfo();
333         final Entry entry = getEntry(displayInfo);
334         if (entry == null) {
335             return false;
336         }
337         return entry.mShouldShowSystemDecors;
338     }
339 
setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow)340     void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
341         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
342             Slog.e(TAG, "Default display should show system decors");
343             return;
344         }
345 
346         final DisplayInfo displayInfo = dc.getDisplayInfo();
347         final Entry entry = getOrCreateEntry(displayInfo);
348         entry.mShouldShowSystemDecors = shouldShow;
349         writeSettingsIfNeeded(entry, displayInfo);
350     }
351 
shouldShowImeLocked(DisplayContent dc)352     boolean shouldShowImeLocked(DisplayContent dc) {
353         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
354             // For default display should shows IME.
355             return true;
356         }
357 
358         final DisplayInfo displayInfo = dc.getDisplayInfo();
359         final Entry entry = getEntry(displayInfo);
360         if (entry == null) {
361             return false;
362         }
363         return entry.mShouldShowIme;
364     }
365 
setShouldShowImeLocked(DisplayContent dc, boolean shouldShow)366     void setShouldShowImeLocked(DisplayContent dc, boolean shouldShow) {
367         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
368             Slog.e(TAG, "Default display should show IME");
369             return;
370         }
371 
372         final DisplayInfo displayInfo = dc.getDisplayInfo();
373         final Entry entry = getOrCreateEntry(displayInfo);
374         entry.mShouldShowIme = shouldShow;
375         writeSettingsIfNeeded(entry, displayInfo);
376     }
377 
applySettingsToDisplayLocked(DisplayContent dc)378     void applySettingsToDisplayLocked(DisplayContent dc) {
379         final DisplayInfo displayInfo = dc.getDisplayInfo();
380         final Entry entry = getOrCreateEntry(displayInfo);
381 
382         // Setting windowing mode first, because it may override overscan values later.
383         dc.setWindowingMode(getWindowingModeLocked(entry, dc));
384 
385         dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
386                 entry.mUserRotation, entry.mFixedToUserRotation);
387 
388         if (entry.mForcedDensity != 0) {
389             dc.mBaseDisplayDensity = entry.mForcedDensity;
390         }
391         if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
392             dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight,
393                     dc.mBaseDisplayDensity);
394         }
395         dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED;
396     }
397 
398     /**
399      * Updates settings for the given display after system features are loaded into window manager
400      * service, e.g. if this device is PC and if this device supports freeform.
401      *
402      * @param dc the given display.
403      * @return {@code true} if any settings for this display has changed; {@code false} if nothing
404      * changed.
405      */
updateSettingsForDisplay(DisplayContent dc)406     boolean updateSettingsForDisplay(DisplayContent dc) {
407         if (dc.getWindowingMode() != getWindowingModeLocked(dc)) {
408             // For the time being the only thing that may change is windowing mode, so just update
409             // that.
410             dc.setWindowingMode(getWindowingModeLocked(dc));
411             return true;
412         }
413         return false;
414     }
415 
readSettings()416     private void readSettings() {
417         InputStream stream;
418         try {
419             stream = mStorage.openRead();
420         } catch (IOException e) {
421             Slog.i(TAG, "No existing display settings, starting empty");
422             return;
423         }
424         boolean success = false;
425         try {
426             XmlPullParser parser = Xml.newPullParser();
427             parser.setInput(stream, StandardCharsets.UTF_8.name());
428             int type;
429             while ((type = parser.next()) != XmlPullParser.START_TAG
430                     && type != XmlPullParser.END_DOCUMENT) {
431                 // Do nothing.
432             }
433 
434             if (type != XmlPullParser.START_TAG) {
435                 throw new IllegalStateException("no start tag found");
436             }
437 
438             int outerDepth = parser.getDepth();
439             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
440                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
441                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
442                     continue;
443                 }
444 
445                 String tagName = parser.getName();
446                 if (tagName.equals("display")) {
447                     readDisplay(parser);
448                 } else if (tagName.equals("config")) {
449                     readConfig(parser);
450                 } else {
451                     Slog.w(TAG, "Unknown element under <display-settings>: "
452                             + parser.getName());
453                     XmlUtils.skipCurrentTag(parser);
454                 }
455             }
456             success = true;
457         } catch (IllegalStateException e) {
458             Slog.w(TAG, "Failed parsing " + e);
459         } catch (NullPointerException e) {
460             Slog.w(TAG, "Failed parsing " + e);
461         } catch (NumberFormatException e) {
462             Slog.w(TAG, "Failed parsing " + e);
463         } catch (XmlPullParserException e) {
464             Slog.w(TAG, "Failed parsing " + e);
465         } catch (IOException e) {
466             Slog.w(TAG, "Failed parsing " + e);
467         } catch (IndexOutOfBoundsException e) {
468             Slog.w(TAG, "Failed parsing " + e);
469         } finally {
470             if (!success) {
471                 mEntries.clear();
472             }
473             try {
474                 stream.close();
475             } catch (IOException e) {
476             }
477         }
478     }
479 
getIntAttribute(XmlPullParser parser, String name)480     private int getIntAttribute(XmlPullParser parser, String name) {
481         return getIntAttribute(parser, name, 0 /* defaultValue */);
482     }
483 
getIntAttribute(XmlPullParser parser, String name, int defaultValue)484     private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) {
485         try {
486             final String str = parser.getAttributeValue(null, name);
487             return str != null ? Integer.parseInt(str) : defaultValue;
488         } catch (NumberFormatException e) {
489             return defaultValue;
490         }
491     }
492 
getBooleanAttribute(XmlPullParser parser, String name)493     private boolean getBooleanAttribute(XmlPullParser parser, String name) {
494         return getBooleanAttribute(parser, name, false /* defaultValue */);
495     }
496 
getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue)497     private boolean getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue) {
498         try {
499             final String str = parser.getAttributeValue(null, name);
500             return str != null ? Boolean.parseBoolean(str) : defaultValue;
501         } catch (NumberFormatException e) {
502             return defaultValue;
503         }
504     }
505 
readDisplay(XmlPullParser parser)506     private void readDisplay(XmlPullParser parser) throws NumberFormatException,
507             XmlPullParserException, IOException {
508         String name = parser.getAttributeValue(null, "name");
509         if (name != null) {
510             Entry entry = new Entry(name);
511             entry.mWindowingMode = getIntAttribute(parser, "windowingMode",
512                     WindowConfiguration.WINDOWING_MODE_UNDEFINED);
513             entry.mUserRotationMode = getIntAttribute(parser, "userRotationMode",
514                     WindowManagerPolicy.USER_ROTATION_FREE);
515             entry.mUserRotation = getIntAttribute(parser, "userRotation",
516                     Surface.ROTATION_0);
517             entry.mForcedWidth = getIntAttribute(parser, "forcedWidth");
518             entry.mForcedHeight = getIntAttribute(parser, "forcedHeight");
519             entry.mForcedDensity = getIntAttribute(parser, "forcedDensity");
520             entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode",
521                     FORCE_SCALING_MODE_AUTO);
522             entry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",
523                     REMOVE_CONTENT_MODE_UNDEFINED);
524             entry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,
525                     "shouldShowWithInsecureKeyguard");
526             entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors");
527             entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme");
528             entry.mFixedToUserRotation = getIntAttribute(parser, "fixedToUserRotation");
529             mEntries.put(name, entry);
530         }
531         XmlUtils.skipCurrentTag(parser);
532     }
533 
readConfig(XmlPullParser parser)534     private void readConfig(XmlPullParser parser) throws NumberFormatException,
535             XmlPullParserException, IOException {
536         mIdentifier = getIntAttribute(parser, "identifier");
537         XmlUtils.skipCurrentTag(parser);
538     }
539 
writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo)540     private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) {
541         if (changedEntry.isEmpty() && !removeEntry(displayInfo)) {
542             // The entry didn't exist so nothing is changed and no need to update the file.
543             return;
544         }
545 
546         mEntries.put(getIdentifier(displayInfo), changedEntry);
547         writeSettings();
548     }
549 
writeSettings()550     private void writeSettings() {
551         OutputStream stream;
552         try {
553             stream = mStorage.startWrite();
554         } catch (IOException e) {
555             Slog.w(TAG, "Failed to write display settings: " + e);
556             return;
557         }
558 
559         try {
560             XmlSerializer out = new FastXmlSerializer();
561             out.setOutput(stream, StandardCharsets.UTF_8.name());
562             out.startDocument(null, true);
563 
564             out.startTag(null, "display-settings");
565 
566             out.startTag(null, "config");
567             out.attribute(null, "identifier", Integer.toString(mIdentifier));
568             out.endTag(null, "config");
569 
570             for (Entry entry : mEntries.values()) {
571                 out.startTag(null, "display");
572                 out.attribute(null, "name", entry.mName);
573                 if (entry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
574                     out.attribute(null, "windowingMode", Integer.toString(entry.mWindowingMode));
575                 }
576                 if (entry.mUserRotationMode != WindowManagerPolicy.USER_ROTATION_FREE) {
577                     out.attribute(null, "userRotationMode",
578                             Integer.toString(entry.mUserRotationMode));
579                 }
580                 if (entry.mUserRotation != Surface.ROTATION_0) {
581                     out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation));
582                 }
583                 if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
584                     out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth));
585                     out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight));
586                 }
587                 if (entry.mForcedDensity != 0) {
588                     out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity));
589                 }
590                 if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) {
591                     out.attribute(null, "forcedScalingMode",
592                             Integer.toString(entry.mForcedScalingMode));
593                 }
594                 if (entry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) {
595                     out.attribute(null, "removeContentMode",
596                             Integer.toString(entry.mRemoveContentMode));
597                 }
598                 if (entry.mShouldShowWithInsecureKeyguard) {
599                     out.attribute(null, "shouldShowWithInsecureKeyguard",
600                             Boolean.toString(entry.mShouldShowWithInsecureKeyguard));
601                 }
602                 if (entry.mShouldShowSystemDecors) {
603                     out.attribute(null, "shouldShowSystemDecors",
604                             Boolean.toString(entry.mShouldShowSystemDecors));
605                 }
606                 if (entry.mShouldShowIme) {
607                     out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme));
608                 }
609                 if (entry.mFixedToUserRotation != IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT) {
610                     out.attribute(null, "fixedToUserRotation",
611                             Integer.toString(entry.mFixedToUserRotation));
612                 }
613                 out.endTag(null, "display");
614             }
615 
616             out.endTag(null, "display-settings");
617             out.endDocument();
618             mStorage.finishWrite(stream, true /* success */);
619         } catch (IOException e) {
620             Slog.w(TAG, "Failed to write display window settings.", e);
621             mStorage.finishWrite(stream, false /* success */);
622         }
623     }
624 
625     /**
626      * Removes an entry from {@link #mEntries} cache. Looks up by new and previously used
627      * identifiers.
628      */
removeEntry(DisplayInfo displayInfo)629     private boolean removeEntry(DisplayInfo displayInfo) {
630         // Remove entry based on primary identifier.
631         boolean removed = mEntries.remove(getIdentifier(displayInfo)) != null;
632         // Ensure that legacy entries are cleared as well.
633         removed |= mEntries.remove(displayInfo.uniqueId) != null;
634         removed |= mEntries.remove(displayInfo.name) != null;
635         return removed;
636     }
637 
638     /** Gets the identifier of choice for the current config. */
getIdentifier(DisplayInfo displayInfo)639     private String getIdentifier(DisplayInfo displayInfo) {
640         if (mIdentifier == IDENTIFIER_PORT && displayInfo.address != null) {
641             // Config suggests using port as identifier for physical displays.
642             if (displayInfo.address instanceof DisplayAddress.Physical) {
643                 byte port = ((DisplayAddress.Physical) displayInfo.address).getPort();
644                 return "port:" + Byte.toUnsignedInt(port);
645             }
646         }
647         return displayInfo.uniqueId;
648     }
649 
650     private static class AtomicFileStorage implements SettingPersister {
651         private final AtomicFile mAtomicFile;
652 
AtomicFileStorage()653         AtomicFileStorage() {
654             final File folder = new File(Environment.getDataDirectory(), SYSTEM_DIRECTORY);
655             final File settingsFile = new File(folder, DISPLAY_SETTINGS_FILE_NAME);
656             // If display_settings.xml doesn't exist, try to copy the vendor's one instead
657             // in order to provide the vendor specific initialization.
658             if (!settingsFile.exists()) {
659                 copyVendorSettings(settingsFile);
660             }
661             mAtomicFile = new AtomicFile(settingsFile, WM_DISPLAY_COMMIT_TAG);
662         }
663 
copyVendorSettings(File target)664         private static void copyVendorSettings(File target) {
665             final File vendorFile = new File(Environment.getVendorDirectory(),
666                     VENDOR_DISPLAY_SETTINGS_PATH);
667             if (vendorFile.canRead()) {
668                 try {
669                     FileUtils.copy(vendorFile, target);
670                 } catch (IOException e) {
671                     Slog.e(TAG, "Failed to copy vendor display_settings.xml");
672                 }
673             }
674         }
675 
676         @Override
openRead()677         public InputStream openRead() throws FileNotFoundException {
678             return mAtomicFile.openRead();
679         }
680 
681         @Override
startWrite()682         public OutputStream startWrite() throws IOException {
683             return mAtomicFile.startWrite();
684         }
685 
686         @Override
finishWrite(OutputStream os, boolean success)687         public void finishWrite(OutputStream os, boolean success) {
688             if (!(os instanceof FileOutputStream)) {
689                 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os);
690             }
691             FileOutputStream fos = (FileOutputStream) os;
692             if (success) {
693                 mAtomicFile.finishWrite(fos);
694             } else {
695                 mAtomicFile.failWrite(fos);
696             }
697         }
698     }
699 }
700