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, int displayId)251     private int getWindowingModeLocked(Entry entry, int displayId) {
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             final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays
263                     && displayId != Display.DEFAULT_DISPLAY;
264             windowingMode = mService.mAtmService.mSupportsFreeformWindowManagement
265                     && (mService.mIsPc || forceDesktopMode)
266                     ? WindowConfiguration.WINDOWING_MODE_FREEFORM
267                     : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
268         }
269         return windowingMode;
270     }
271 
getWindowingModeLocked(DisplayContent dc)272     int getWindowingModeLocked(DisplayContent dc) {
273         final DisplayInfo displayInfo = dc.getDisplayInfo();
274         final Entry entry = getEntry(displayInfo);
275         return getWindowingModeLocked(entry, dc.getDisplayId());
276     }
277 
setWindowingModeLocked(DisplayContent dc, int mode)278     void setWindowingModeLocked(DisplayContent dc, int mode) {
279         final DisplayInfo displayInfo = dc.getDisplayInfo();
280         final Entry entry = getOrCreateEntry(displayInfo);
281         entry.mWindowingMode = mode;
282         dc.setWindowingMode(mode);
283         writeSettingsIfNeeded(entry, displayInfo);
284     }
285 
getRemoveContentModeLocked(DisplayContent dc)286     int getRemoveContentModeLocked(DisplayContent dc) {
287         final DisplayInfo displayInfo = dc.getDisplayInfo();
288         final Entry entry = getEntry(displayInfo);
289         if (entry == null || entry.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
290             if (dc.isPrivate()) {
291                 // For private displays by default content is destroyed on removal.
292                 return REMOVE_CONTENT_MODE_DESTROY;
293             }
294             // For other displays by default content is moved to primary on removal.
295             return REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
296         }
297         return entry.mRemoveContentMode;
298     }
299 
setRemoveContentModeLocked(DisplayContent dc, int mode)300     void setRemoveContentModeLocked(DisplayContent dc, int mode) {
301         final DisplayInfo displayInfo = dc.getDisplayInfo();
302         final Entry entry = getOrCreateEntry(displayInfo);
303         entry.mRemoveContentMode = mode;
304         writeSettingsIfNeeded(entry, displayInfo);
305     }
306 
shouldShowWithInsecureKeyguardLocked(DisplayContent dc)307     boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
308         final DisplayInfo displayInfo = dc.getDisplayInfo();
309         final Entry entry = getEntry(displayInfo);
310         if (entry == null) {
311             return false;
312         }
313         return entry.mShouldShowWithInsecureKeyguard;
314     }
315 
setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow)316     void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
317         if (!dc.isPrivate() && shouldShow) {
318             Slog.e(TAG, "Public display can't be allowed to show content when locked");
319             return;
320         }
321 
322         final DisplayInfo displayInfo = dc.getDisplayInfo();
323         final Entry entry = getOrCreateEntry(displayInfo);
324         entry.mShouldShowWithInsecureKeyguard = shouldShow;
325         writeSettingsIfNeeded(entry, displayInfo);
326     }
327 
shouldShowSystemDecorsLocked(DisplayContent dc)328     boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
329         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
330             // For default display should show system decors.
331             return true;
332         }
333 
334         final DisplayInfo displayInfo = dc.getDisplayInfo();
335         final Entry entry = getEntry(displayInfo);
336         if (entry == null) {
337             return false;
338         }
339         return entry.mShouldShowSystemDecors;
340     }
341 
setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow)342     void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
343         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
344             Slog.e(TAG, "Default display should show system decors");
345             return;
346         }
347 
348         final DisplayInfo displayInfo = dc.getDisplayInfo();
349         final Entry entry = getOrCreateEntry(displayInfo);
350         entry.mShouldShowSystemDecors = shouldShow;
351         writeSettingsIfNeeded(entry, displayInfo);
352     }
353 
shouldShowImeLocked(DisplayContent dc)354     boolean shouldShowImeLocked(DisplayContent dc) {
355         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
356             // For default display should shows IME.
357             return true;
358         }
359 
360         final DisplayInfo displayInfo = dc.getDisplayInfo();
361         final Entry entry = getEntry(displayInfo);
362         if (entry == null) {
363             return false;
364         }
365         return entry.mShouldShowIme;
366     }
367 
setShouldShowImeLocked(DisplayContent dc, boolean shouldShow)368     void setShouldShowImeLocked(DisplayContent dc, boolean shouldShow) {
369         if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
370             Slog.e(TAG, "Default display should show IME");
371             return;
372         }
373 
374         final DisplayInfo displayInfo = dc.getDisplayInfo();
375         final Entry entry = getOrCreateEntry(displayInfo);
376         entry.mShouldShowIme = shouldShow;
377         writeSettingsIfNeeded(entry, displayInfo);
378     }
379 
applySettingsToDisplayLocked(DisplayContent dc)380     void applySettingsToDisplayLocked(DisplayContent dc) {
381         final DisplayInfo displayInfo = dc.getDisplayInfo();
382         final Entry entry = getOrCreateEntry(displayInfo);
383 
384         // Setting windowing mode first, because it may override overscan values later.
385         dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId()));
386 
387         dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
388                 entry.mUserRotation, entry.mFixedToUserRotation);
389 
390         if (entry.mForcedDensity != 0) {
391             dc.mBaseDisplayDensity = entry.mForcedDensity;
392         }
393         if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
394             dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight,
395                     dc.mBaseDisplayDensity);
396         }
397         dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED;
398     }
399 
400     /**
401      * Updates settings for the given display after system features are loaded into window manager
402      * service, e.g. if this device is PC and if this device supports freeform.
403      *
404      * @param dc the given display.
405      * @return {@code true} if any settings for this display has changed; {@code false} if nothing
406      * changed.
407      */
updateSettingsForDisplay(DisplayContent dc)408     boolean updateSettingsForDisplay(DisplayContent dc) {
409         if (dc.getWindowingMode() != getWindowingModeLocked(dc)) {
410             // For the time being the only thing that may change is windowing mode, so just update
411             // that.
412             dc.setWindowingMode(getWindowingModeLocked(dc));
413             return true;
414         }
415         return false;
416     }
417 
readSettings()418     private void readSettings() {
419         InputStream stream;
420         try {
421             stream = mStorage.openRead();
422         } catch (IOException e) {
423             Slog.i(TAG, "No existing display settings, starting empty");
424             return;
425         }
426         boolean success = false;
427         try {
428             XmlPullParser parser = Xml.newPullParser();
429             parser.setInput(stream, StandardCharsets.UTF_8.name());
430             int type;
431             while ((type = parser.next()) != XmlPullParser.START_TAG
432                     && type != XmlPullParser.END_DOCUMENT) {
433                 // Do nothing.
434             }
435 
436             if (type != XmlPullParser.START_TAG) {
437                 throw new IllegalStateException("no start tag found");
438             }
439 
440             int outerDepth = parser.getDepth();
441             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
442                     && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
443                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
444                     continue;
445                 }
446 
447                 String tagName = parser.getName();
448                 if (tagName.equals("display")) {
449                     readDisplay(parser);
450                 } else if (tagName.equals("config")) {
451                     readConfig(parser);
452                 } else {
453                     Slog.w(TAG, "Unknown element under <display-settings>: "
454                             + parser.getName());
455                     XmlUtils.skipCurrentTag(parser);
456                 }
457             }
458             success = true;
459         } catch (IllegalStateException e) {
460             Slog.w(TAG, "Failed parsing " + e);
461         } catch (NullPointerException e) {
462             Slog.w(TAG, "Failed parsing " + e);
463         } catch (NumberFormatException e) {
464             Slog.w(TAG, "Failed parsing " + e);
465         } catch (XmlPullParserException e) {
466             Slog.w(TAG, "Failed parsing " + e);
467         } catch (IOException e) {
468             Slog.w(TAG, "Failed parsing " + e);
469         } catch (IndexOutOfBoundsException e) {
470             Slog.w(TAG, "Failed parsing " + e);
471         } finally {
472             if (!success) {
473                 mEntries.clear();
474             }
475             try {
476                 stream.close();
477             } catch (IOException e) {
478             }
479         }
480     }
481 
getIntAttribute(XmlPullParser parser, String name)482     private int getIntAttribute(XmlPullParser parser, String name) {
483         return getIntAttribute(parser, name, 0 /* defaultValue */);
484     }
485 
getIntAttribute(XmlPullParser parser, String name, int defaultValue)486     private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) {
487         try {
488             final String str = parser.getAttributeValue(null, name);
489             return str != null ? Integer.parseInt(str) : defaultValue;
490         } catch (NumberFormatException e) {
491             return defaultValue;
492         }
493     }
494 
getBooleanAttribute(XmlPullParser parser, String name)495     private boolean getBooleanAttribute(XmlPullParser parser, String name) {
496         return getBooleanAttribute(parser, name, false /* defaultValue */);
497     }
498 
getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue)499     private boolean getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue) {
500         try {
501             final String str = parser.getAttributeValue(null, name);
502             return str != null ? Boolean.parseBoolean(str) : defaultValue;
503         } catch (NumberFormatException e) {
504             return defaultValue;
505         }
506     }
507 
readDisplay(XmlPullParser parser)508     private void readDisplay(XmlPullParser parser) throws NumberFormatException,
509             XmlPullParserException, IOException {
510         String name = parser.getAttributeValue(null, "name");
511         if (name != null) {
512             Entry entry = new Entry(name);
513             entry.mWindowingMode = getIntAttribute(parser, "windowingMode",
514                     WindowConfiguration.WINDOWING_MODE_UNDEFINED);
515             entry.mUserRotationMode = getIntAttribute(parser, "userRotationMode",
516                     WindowManagerPolicy.USER_ROTATION_FREE);
517             entry.mUserRotation = getIntAttribute(parser, "userRotation",
518                     Surface.ROTATION_0);
519             entry.mForcedWidth = getIntAttribute(parser, "forcedWidth");
520             entry.mForcedHeight = getIntAttribute(parser, "forcedHeight");
521             entry.mForcedDensity = getIntAttribute(parser, "forcedDensity");
522             entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode",
523                     FORCE_SCALING_MODE_AUTO);
524             entry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",
525                     REMOVE_CONTENT_MODE_UNDEFINED);
526             entry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,
527                     "shouldShowWithInsecureKeyguard");
528             entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors");
529             entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme");
530             entry.mFixedToUserRotation = getIntAttribute(parser, "fixedToUserRotation");
531             mEntries.put(name, entry);
532         }
533         XmlUtils.skipCurrentTag(parser);
534     }
535 
readConfig(XmlPullParser parser)536     private void readConfig(XmlPullParser parser) throws NumberFormatException,
537             XmlPullParserException, IOException {
538         mIdentifier = getIntAttribute(parser, "identifier");
539         XmlUtils.skipCurrentTag(parser);
540     }
541 
writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo)542     private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) {
543         if (changedEntry.isEmpty() && !removeEntry(displayInfo)) {
544             // The entry didn't exist so nothing is changed and no need to update the file.
545             return;
546         }
547 
548         mEntries.put(getIdentifier(displayInfo), changedEntry);
549         writeSettings();
550     }
551 
writeSettings()552     private void writeSettings() {
553         OutputStream stream;
554         try {
555             stream = mStorage.startWrite();
556         } catch (IOException e) {
557             Slog.w(TAG, "Failed to write display settings: " + e);
558             return;
559         }
560 
561         try {
562             XmlSerializer out = new FastXmlSerializer();
563             out.setOutput(stream, StandardCharsets.UTF_8.name());
564             out.startDocument(null, true);
565 
566             out.startTag(null, "display-settings");
567 
568             out.startTag(null, "config");
569             out.attribute(null, "identifier", Integer.toString(mIdentifier));
570             out.endTag(null, "config");
571 
572             for (Entry entry : mEntries.values()) {
573                 out.startTag(null, "display");
574                 out.attribute(null, "name", entry.mName);
575                 if (entry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
576                     out.attribute(null, "windowingMode", Integer.toString(entry.mWindowingMode));
577                 }
578                 if (entry.mUserRotationMode != WindowManagerPolicy.USER_ROTATION_FREE) {
579                     out.attribute(null, "userRotationMode",
580                             Integer.toString(entry.mUserRotationMode));
581                 }
582                 if (entry.mUserRotation != Surface.ROTATION_0) {
583                     out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation));
584                 }
585                 if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
586                     out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth));
587                     out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight));
588                 }
589                 if (entry.mForcedDensity != 0) {
590                     out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity));
591                 }
592                 if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) {
593                     out.attribute(null, "forcedScalingMode",
594                             Integer.toString(entry.mForcedScalingMode));
595                 }
596                 if (entry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) {
597                     out.attribute(null, "removeContentMode",
598                             Integer.toString(entry.mRemoveContentMode));
599                 }
600                 if (entry.mShouldShowWithInsecureKeyguard) {
601                     out.attribute(null, "shouldShowWithInsecureKeyguard",
602                             Boolean.toString(entry.mShouldShowWithInsecureKeyguard));
603                 }
604                 if (entry.mShouldShowSystemDecors) {
605                     out.attribute(null, "shouldShowSystemDecors",
606                             Boolean.toString(entry.mShouldShowSystemDecors));
607                 }
608                 if (entry.mShouldShowIme) {
609                     out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme));
610                 }
611                 if (entry.mFixedToUserRotation != IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT) {
612                     out.attribute(null, "fixedToUserRotation",
613                             Integer.toString(entry.mFixedToUserRotation));
614                 }
615                 out.endTag(null, "display");
616             }
617 
618             out.endTag(null, "display-settings");
619             out.endDocument();
620             mStorage.finishWrite(stream, true /* success */);
621         } catch (IOException e) {
622             Slog.w(TAG, "Failed to write display window settings.", e);
623             mStorage.finishWrite(stream, false /* success */);
624         }
625     }
626 
627     /**
628      * Removes an entry from {@link #mEntries} cache. Looks up by new and previously used
629      * identifiers.
630      */
removeEntry(DisplayInfo displayInfo)631     private boolean removeEntry(DisplayInfo displayInfo) {
632         // Remove entry based on primary identifier.
633         boolean removed = mEntries.remove(getIdentifier(displayInfo)) != null;
634         // Ensure that legacy entries are cleared as well.
635         removed |= mEntries.remove(displayInfo.uniqueId) != null;
636         removed |= mEntries.remove(displayInfo.name) != null;
637         return removed;
638     }
639 
640     /** Gets the identifier of choice for the current config. */
getIdentifier(DisplayInfo displayInfo)641     private String getIdentifier(DisplayInfo displayInfo) {
642         if (mIdentifier == IDENTIFIER_PORT && displayInfo.address != null) {
643             // Config suggests using port as identifier for physical displays.
644             if (displayInfo.address instanceof DisplayAddress.Physical) {
645                 byte port = ((DisplayAddress.Physical) displayInfo.address).getPort();
646                 return "port:" + Byte.toUnsignedInt(port);
647             }
648         }
649         return displayInfo.uniqueId;
650     }
651 
652     private static class AtomicFileStorage implements SettingPersister {
653         private final AtomicFile mAtomicFile;
654 
AtomicFileStorage()655         AtomicFileStorage() {
656             final File folder = new File(Environment.getDataDirectory(), SYSTEM_DIRECTORY);
657             final File settingsFile = new File(folder, DISPLAY_SETTINGS_FILE_NAME);
658             // If display_settings.xml doesn't exist, try to copy the vendor's one instead
659             // in order to provide the vendor specific initialization.
660             if (!settingsFile.exists()) {
661                 copyVendorSettings(settingsFile);
662             }
663             mAtomicFile = new AtomicFile(settingsFile, WM_DISPLAY_COMMIT_TAG);
664         }
665 
copyVendorSettings(File target)666         private static void copyVendorSettings(File target) {
667             final File vendorFile = new File(Environment.getVendorDirectory(),
668                     VENDOR_DISPLAY_SETTINGS_PATH);
669             if (vendorFile.canRead()) {
670                 try {
671                     FileUtils.copy(vendorFile, target);
672                 } catch (IOException e) {
673                     Slog.e(TAG, "Failed to copy vendor display_settings.xml");
674                 }
675             }
676         }
677 
678         @Override
openRead()679         public InputStream openRead() throws FileNotFoundException {
680             return mAtomicFile.openRead();
681         }
682 
683         @Override
startWrite()684         public OutputStream startWrite() throws IOException {
685             return mAtomicFile.startWrite();
686         }
687 
688         @Override
finishWrite(OutputStream os, boolean success)689         public void finishWrite(OutputStream os, boolean success) {
690             if (!(os instanceof FileOutputStream)) {
691                 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os);
692             }
693             FileOutputStream fos = (FileOutputStream) os;
694             if (success) {
695                 mAtomicFile.finishWrite(fos);
696             } else {
697                 mAtomicFile.failWrite(fos);
698             }
699         }
700     }
701 }
702