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