• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.display;
18 
19 import android.annotation.Nullable;
20 import android.graphics.Point;
21 import android.hardware.display.BrightnessConfiguration;
22 import android.hardware.display.WifiDisplay;
23 import android.os.Handler;
24 import android.util.AtomicFile;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 import android.util.SparseLongArray;
28 import android.util.TimeUtils;
29 import android.util.TypedXmlPullParser;
30 import android.util.TypedXmlSerializer;
31 import android.util.Xml;
32 import android.view.Display;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.os.BackgroundThread;
36 import com.android.internal.util.XmlUtils;
37 
38 import libcore.io.IoUtils;
39 
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.ByteArrayOutputStream;
43 import java.io.File;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49 import java.io.PrintWriter;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.Map;
53 import java.util.Objects;
54 
55 /**
56  * Manages persistent state recorded by the display manager service as an XML file.
57  * Caller must acquire lock on the data store before accessing it.
58  *
59  * File format:
60  * <code>
61  * &lt;display-manager-state>
62  *   &lt;remembered-wifi-displays>
63  *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
64  *   &lt;remembered-wifi-displays>
65  *   &lt;display-states>
66  *      &lt;display unique-id="XXXXXXX">
67  *          &lt;color-mode>0&lt;/color-mode>
68  *          &lt;brightness-value>0&lt;/brightness-value>
69  *          &lt;brightness-configurations>
70  *              &lt;brightness-configuration user-serial="0" package-name="com.example"
71  *              timestamp="1234">
72  *                  &lt;brightness-curve description="some text">
73  *                      &lt;brightness-point lux="0" nits="13.25"/>
74  *                      &lt;brightness-point lux="20" nits="35.94"/>
75  *                  &lt;/brightness-curve>
76  *              &lt;/brightness-configuration>
77  *          &lt;/brightness-configurations>
78  *          &lt;display-mode>0&lt;
79  *              &lt;resolution-width>1080&lt;/resolution-width>
80  *              &lt;resolution-height>1920&lt;/resolution-height>
81  *              &lt;refresh-rate>60&lt;/refresh-rate>
82  *          &lt;/display-mode>
83  *      &lt;/display>
84  *  &lt;/display-states>
85  *  &lt;stable-device-values>
86  *      &lt;stable-display-height>1920&lt;/stable-display-height>
87  *      &lt;stable-display-width>1080&lt;/stable-display-width>
88  *  &lt;/stable-device-values>
89  *  &lt;brightness-configurations>
90  *      &lt;brightness-configuration user-serial="0" package-name="com.example" timestamp="1234">
91  *          &lt;brightness-curve description="some text">
92  *              &lt;brightness-point lux="0" nits="13.25"/>
93  *              &lt;brightness-point lux="20" nits="35.94"/>
94  *          &lt;/brightness-curve>
95  *      &lt;/brightness-configuration>
96  *  &lt;/brightness-configurations>
97  *  &lt;brightness-nits-for-default-display>600&lt;/brightness-nits-for-default-display>
98  * &lt;/display-manager-state>
99  * </code>
100  *
101  * TODO: refactor this to extract common code shared with the input manager's data store
102  */
103 final class PersistentDataStore {
104     static final String TAG = "DisplayManager.PersistentDataStore";
105 
106     private static final String TAG_DISPLAY_MANAGER_STATE = "display-manager-state";
107 
108     private static final String TAG_REMEMBERED_WIFI_DISPLAYS = "remembered-wifi-displays";
109     private static final String TAG_WIFI_DISPLAY = "wifi-display";
110     private static final String ATTR_DEVICE_ADDRESS = "deviceAddress";
111     private static final String ATTR_DEVICE_NAME = "deviceName";
112     private static final String ATTR_DEVICE_ALIAS = "deviceAlias";
113 
114     private static final String TAG_DISPLAY_STATES = "display-states";
115     private static final String TAG_DISPLAY = "display";
116     private static final String TAG_COLOR_MODE = "color-mode";
117     private static final String TAG_BRIGHTNESS_VALUE = "brightness-value";
118     private static final String ATTR_UNIQUE_ID = "unique-id";
119 
120     private static final String TAG_STABLE_DEVICE_VALUES = "stable-device-values";
121     private static final String TAG_STABLE_DISPLAY_HEIGHT = "stable-display-height";
122     private static final String TAG_STABLE_DISPLAY_WIDTH = "stable-display-width";
123 
124     private static final String TAG_BRIGHTNESS_CONFIGURATIONS = "brightness-configurations";
125     private static final String TAG_BRIGHTNESS_CONFIGURATION = "brightness-configuration";
126     private static final String ATTR_USER_SERIAL = "user-serial";
127     private static final String ATTR_PACKAGE_NAME = "package-name";
128     private static final String ATTR_TIME_STAMP = "timestamp";
129 
130     private static final String TAG_RESOLUTION_WIDTH = "resolution-width";
131     private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
132     private static final String TAG_REFRESH_RATE = "refresh-rate";
133 
134     private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
135             "brightness-nits-for-default-display";
136 
137     // Remembered Wifi display devices.
138     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
139 
140     // Display state by unique id.
141     private final HashMap<String, DisplayState> mDisplayStates =
142             new HashMap<String, DisplayState>();
143 
144     private float mBrightnessNitsForDefaultDisplay = -1;
145 
146     // Display values which should be stable across the device's lifetime.
147     private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
148 
149     // Brightness configuration by user
150     private BrightnessConfigurations mGlobalBrightnessConfigurations =
151             new BrightnessConfigurations();
152 
153     // True if the data has been loaded.
154     private boolean mLoaded;
155 
156     // True if there are changes to be saved.
157     private boolean mDirty;
158 
159     // The interface for methods which should be replaced by the test harness.
160     private Injector mInjector;
161 
162     private final Handler mHandler;
163     private final Object mFileAccessLock = new Object();
164 
PersistentDataStore()165     public PersistentDataStore() {
166         this(new Injector());
167     }
168 
169     @VisibleForTesting
PersistentDataStore(Injector injector)170     PersistentDataStore(Injector injector) {
171         this(injector, new Handler(BackgroundThread.getHandler().getLooper()));
172     }
173 
174     @VisibleForTesting
PersistentDataStore(Injector injector, Handler handler)175     PersistentDataStore(Injector injector, Handler handler) {
176         mInjector = injector;
177         mHandler = handler;
178     }
179 
saveIfNeeded()180     public void saveIfNeeded() {
181         if (mDirty) {
182             save();
183             mDirty = false;
184         }
185     }
186 
getRememberedWifiDisplay(String deviceAddress)187     public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
188         loadIfNeeded();
189         int index = findRememberedWifiDisplay(deviceAddress);
190         if (index >= 0) {
191             return mRememberedWifiDisplays.get(index);
192         }
193         return null;
194     }
195 
getRememberedWifiDisplays()196     public WifiDisplay[] getRememberedWifiDisplays() {
197         loadIfNeeded();
198         return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
199     }
200 
applyWifiDisplayAlias(WifiDisplay display)201     public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
202         if (display != null) {
203             loadIfNeeded();
204 
205             String alias = null;
206             int index = findRememberedWifiDisplay(display.getDeviceAddress());
207             if (index >= 0) {
208                 alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
209             }
210             if (!Objects.equals(display.getDeviceAlias(), alias)) {
211                 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
212                         alias, display.isAvailable(), display.canConnect(), display.isRemembered());
213             }
214         }
215         return display;
216     }
217 
applyWifiDisplayAliases(WifiDisplay[] displays)218     public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
219         WifiDisplay[] results = displays;
220         if (results != null) {
221             int count = displays.length;
222             for (int i = 0; i < count; i++) {
223                 WifiDisplay result = applyWifiDisplayAlias(displays[i]);
224                 if (result != displays[i]) {
225                     if (results == displays) {
226                         results = new WifiDisplay[count];
227                         System.arraycopy(displays, 0, results, 0, count);
228                     }
229                     results[i] = result;
230                 }
231             }
232         }
233         return results;
234     }
235 
rememberWifiDisplay(WifiDisplay display)236     public boolean rememberWifiDisplay(WifiDisplay display) {
237         loadIfNeeded();
238 
239         int index = findRememberedWifiDisplay(display.getDeviceAddress());
240         if (index >= 0) {
241             WifiDisplay other = mRememberedWifiDisplays.get(index);
242             if (other.equals(display)) {
243                 return false; // already remembered without change
244             }
245             mRememberedWifiDisplays.set(index, display);
246         } else {
247             mRememberedWifiDisplays.add(display);
248         }
249         setDirty();
250         return true;
251     }
252 
forgetWifiDisplay(String deviceAddress)253     public boolean forgetWifiDisplay(String deviceAddress) {
254         loadIfNeeded();
255         int index = findRememberedWifiDisplay(deviceAddress);
256         if (index >= 0) {
257             mRememberedWifiDisplays.remove(index);
258             setDirty();
259             return true;
260         }
261         return false;
262     }
263 
findRememberedWifiDisplay(String deviceAddress)264     private int findRememberedWifiDisplay(String deviceAddress) {
265         int count = mRememberedWifiDisplays.size();
266         for (int i = 0; i < count; i++) {
267             if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
268                 return i;
269             }
270         }
271         return -1;
272     }
273 
getColorMode(DisplayDevice device)274     public int getColorMode(DisplayDevice device) {
275         if (!device.hasStableUniqueId()) {
276             return Display.COLOR_MODE_INVALID;
277         }
278         DisplayState state = getDisplayState(device.getUniqueId(), false);
279         if (state == null) {
280             return Display.COLOR_MODE_INVALID;
281         }
282         return state.getColorMode();
283     }
284 
setColorMode(DisplayDevice device, int colorMode)285     public boolean setColorMode(DisplayDevice device, int colorMode) {
286         if (!device.hasStableUniqueId()) {
287             return false;
288         }
289         DisplayState state = getDisplayState(device.getUniqueId(), true);
290         if (state.setColorMode(colorMode)) {
291             setDirty();
292             return true;
293         }
294         return false;
295     }
296 
getBrightness(DisplayDevice device)297     public float getBrightness(DisplayDevice device) {
298         if (device == null || !device.hasStableUniqueId()) {
299             return Float.NaN;
300         }
301         final DisplayState state = getDisplayState(device.getUniqueId(), false);
302         if (state == null) {
303             return Float.NaN;
304         }
305         return state.getBrightness();
306     }
307 
setBrightness(DisplayDevice displayDevice, float brightness)308     public boolean setBrightness(DisplayDevice displayDevice, float brightness) {
309         final String displayDeviceUniqueId = displayDevice.getUniqueId();
310         if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
311             return false;
312         }
313         final DisplayState state = getDisplayState(displayDeviceUniqueId, true);
314         if (state.setBrightness(brightness)) {
315             setDirty();
316             return true;
317         }
318         return false;
319     }
320 
getBrightnessNitsForDefaultDisplay()321     public float getBrightnessNitsForDefaultDisplay() {
322         return mBrightnessNitsForDefaultDisplay;
323     }
324 
setBrightnessNitsForDefaultDisplay(float nits)325     public boolean setBrightnessNitsForDefaultDisplay(float nits) {
326         if (nits != mBrightnessNitsForDefaultDisplay) {
327             mBrightnessNitsForDefaultDisplay = nits;
328             setDirty();
329             return true;
330         }
331         return false;
332     }
333 
setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate)334     public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
335         final String displayDeviceUniqueId = displayDevice.getUniqueId();
336         if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
337             return false;
338         }
339         DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
340         if (state.setRefreshRate(refreshRate)) {
341             setDirty();
342             return true;
343         }
344         return false;
345     }
346 
getUserPreferredRefreshRate(DisplayDevice device)347     public float getUserPreferredRefreshRate(DisplayDevice device) {
348         if (device == null || !device.hasStableUniqueId()) {
349             return Float.NaN;
350         }
351         final DisplayState state = getDisplayState(device.getUniqueId(), false);
352         if (state == null) {
353             return Float.NaN;
354         }
355         return state.getRefreshRate();
356     }
357 
setUserPreferredResolution(DisplayDevice displayDevice, int width, int height)358     public boolean setUserPreferredResolution(DisplayDevice displayDevice, int width, int height) {
359         final String displayDeviceUniqueId = displayDevice.getUniqueId();
360         if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
361             return false;
362         }
363         DisplayState state = getDisplayState(displayDevice.getUniqueId(), true);
364         if (state.setResolution(width, height)) {
365             setDirty();
366             return true;
367         }
368         return false;
369     }
370 
getUserPreferredResolution(DisplayDevice displayDevice)371     public Point getUserPreferredResolution(DisplayDevice displayDevice) {
372         if (displayDevice == null || !displayDevice.hasStableUniqueId()) {
373             return null;
374         }
375         final DisplayState state = getDisplayState(displayDevice.getUniqueId(), false);
376         if (state == null) {
377             return null;
378         }
379         return state.getResolution();
380     }
381 
getStableDisplaySize()382     public Point getStableDisplaySize() {
383         loadIfNeeded();
384         return mStableDeviceValues.getDisplaySize();
385     }
386 
setStableDisplaySize(Point size)387     public void setStableDisplaySize(Point size) {
388         loadIfNeeded();
389         if (mStableDeviceValues.setDisplaySize(size)) {
390             setDirty();
391         }
392     }
393 
394     // Used for testing & reset
setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, @Nullable String packageName)395     public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial,
396             @Nullable String packageName) {
397         loadIfNeeded();
398         if (mGlobalBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial,
399                 packageName)) {
400 
401             setDirty();
402         }
403     }
404 
setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration, DisplayDevice device, int userSerial, String packageName)405     public boolean setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration,
406             DisplayDevice device, int userSerial, String packageName) {
407         if (device == null || !device.hasStableUniqueId()) {
408             return false;
409         }
410         DisplayState state = getDisplayState(device.getUniqueId(), /*createIfAbsent*/ true);
411         if (state.setBrightnessConfiguration(configuration, userSerial, packageName)) {
412             setDirty();
413             return true;
414         }
415         return false;
416     }
417 
418 
getBrightnessConfigurationForDisplayLocked( String uniqueDisplayId, int userSerial)419     public BrightnessConfiguration getBrightnessConfigurationForDisplayLocked(
420             String uniqueDisplayId, int userSerial) {
421         loadIfNeeded();
422         DisplayState state = mDisplayStates.get(uniqueDisplayId);
423         if (state != null) {
424             return state.getBrightnessConfiguration(userSerial);
425         }
426         return null;
427     }
428 
getBrightnessConfiguration(int userSerial)429     public BrightnessConfiguration getBrightnessConfiguration(int userSerial) {
430         loadIfNeeded();
431         return mGlobalBrightnessConfigurations.getBrightnessConfiguration(userSerial);
432     }
433 
getDisplayState(String uniqueId, boolean createIfAbsent)434     private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
435         loadIfNeeded();
436         DisplayState state = mDisplayStates.get(uniqueId);
437         if (state == null && createIfAbsent) {
438             state = new DisplayState();
439             mDisplayStates.put(uniqueId, state);
440             setDirty();
441         }
442         return state;
443     }
444 
loadIfNeeded()445     public void loadIfNeeded() {
446         if (!mLoaded) {
447             load();
448             mLoaded = true;
449         }
450     }
451 
setDirty()452     private void setDirty() {
453         mDirty = true;
454     }
455 
clearState()456     private void clearState() {
457         mRememberedWifiDisplays.clear();
458     }
459 
load()460     private void load() {
461         synchronized (mFileAccessLock) {
462             clearState();
463 
464             final InputStream is;
465             try {
466                 is = mInjector.openRead();
467             } catch (FileNotFoundException ex) {
468                 return;
469             }
470 
471             TypedXmlPullParser parser;
472             try {
473                 parser = Xml.resolvePullParser(is);
474                 loadFromXml(parser);
475             } catch (IOException ex) {
476                 Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
477                 clearState();
478             } catch (XmlPullParserException ex) {
479                 Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
480                 clearState();
481             } finally {
482                 IoUtils.closeQuietly(is);
483             }
484         }
485     }
486 
save()487     private void save() {
488         final ByteArrayOutputStream os;
489         try {
490             os = new ByteArrayOutputStream();
491 
492             TypedXmlSerializer serializer = Xml.resolveSerializer(os);
493             saveToXml(serializer);
494             serializer.flush();
495 
496             mHandler.removeCallbacksAndMessages(/* token */ null);
497             mHandler.post(() -> {
498                 synchronized (mFileAccessLock) {
499                     OutputStream fileOutput = null;
500                     try {
501                         fileOutput = mInjector.startWrite();
502                         os.writeTo(fileOutput);
503                         fileOutput.flush();
504                     } catch (IOException ex) {
505                         Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
506                     } finally {
507                         if (fileOutput != null) {
508                             mInjector.finishWrite(fileOutput, true);
509                         }
510                     }
511                 }
512             });
513         } catch (IOException ex) {
514             Slog.w(TAG, "Failed to process the XML serializer.", ex);
515         }
516     }
517 
loadFromXml(TypedXmlPullParser parser)518     private void loadFromXml(TypedXmlPullParser parser)
519             throws IOException, XmlPullParserException {
520         XmlUtils.beginDocument(parser, TAG_DISPLAY_MANAGER_STATE);
521         final int outerDepth = parser.getDepth();
522         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
523             if (parser.getName().equals(TAG_REMEMBERED_WIFI_DISPLAYS)) {
524                 loadRememberedWifiDisplaysFromXml(parser);
525             }
526             if (parser.getName().equals(TAG_DISPLAY_STATES)) {
527                 loadDisplaysFromXml(parser);
528             }
529             if (parser.getName().equals(TAG_STABLE_DEVICE_VALUES)) {
530                 mStableDeviceValues.loadFromXml(parser);
531             }
532             if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) {
533                 mGlobalBrightnessConfigurations.loadFromXml(parser);
534             }
535             if (parser.getName().equals(TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY)) {
536                 String value = parser.nextText();
537                 mBrightnessNitsForDefaultDisplay = Float.parseFloat(value);
538             }
539         }
540     }
541 
loadRememberedWifiDisplaysFromXml(TypedXmlPullParser parser)542     private void loadRememberedWifiDisplaysFromXml(TypedXmlPullParser parser)
543             throws IOException, XmlPullParserException {
544         final int outerDepth = parser.getDepth();
545         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
546             if (parser.getName().equals(TAG_WIFI_DISPLAY)) {
547                 String deviceAddress = parser.getAttributeValue(null, ATTR_DEVICE_ADDRESS);
548                 String deviceName = parser.getAttributeValue(null, ATTR_DEVICE_NAME);
549                 String deviceAlias = parser.getAttributeValue(null, ATTR_DEVICE_ALIAS);
550                 if (deviceAddress == null || deviceName == null) {
551                     throw new XmlPullParserException(
552                             "Missing deviceAddress or deviceName attribute on wifi-display.");
553                 }
554                 if (findRememberedWifiDisplay(deviceAddress) >= 0) {
555                     throw new XmlPullParserException(
556                             "Found duplicate wifi display device address.");
557                 }
558 
559                 mRememberedWifiDisplays.add(
560                         new WifiDisplay(deviceAddress, deviceName, deviceAlias,
561                                 false, false, false));
562             }
563         }
564     }
565 
loadDisplaysFromXml(TypedXmlPullParser parser)566     private void loadDisplaysFromXml(TypedXmlPullParser parser)
567             throws IOException, XmlPullParserException {
568         final int outerDepth = parser.getDepth();
569         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
570             if (parser.getName().equals(TAG_DISPLAY)) {
571                 String uniqueId = parser.getAttributeValue(null, ATTR_UNIQUE_ID);
572                 if (uniqueId == null) {
573                     throw new XmlPullParserException(
574                             "Missing unique-id attribute on display.");
575                 }
576                 if (mDisplayStates.containsKey(uniqueId)) {
577                     throw new XmlPullParserException("Found duplicate display.");
578                 }
579 
580                 DisplayState state = new DisplayState();
581                 state.loadFromXml(parser);
582                 mDisplayStates.put(uniqueId, state);
583             }
584         }
585     }
586 
saveToXml(TypedXmlSerializer serializer)587     private void saveToXml(TypedXmlSerializer serializer) throws IOException {
588         serializer.startDocument(null, true);
589         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
590         serializer.startTag(null, TAG_DISPLAY_MANAGER_STATE);
591         serializer.startTag(null, TAG_REMEMBERED_WIFI_DISPLAYS);
592         for (WifiDisplay display : mRememberedWifiDisplays) {
593             serializer.startTag(null, TAG_WIFI_DISPLAY);
594             serializer.attribute(null, ATTR_DEVICE_ADDRESS, display.getDeviceAddress());
595             serializer.attribute(null, ATTR_DEVICE_NAME, display.getDeviceName());
596             if (display.getDeviceAlias() != null) {
597                 serializer.attribute(null, ATTR_DEVICE_ALIAS, display.getDeviceAlias());
598             }
599             serializer.endTag(null, TAG_WIFI_DISPLAY);
600         }
601         serializer.endTag(null, TAG_REMEMBERED_WIFI_DISPLAYS);
602         serializer.startTag(null, TAG_DISPLAY_STATES);
603         for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
604             final String uniqueId = entry.getKey();
605             final DisplayState state = entry.getValue();
606             serializer.startTag(null, TAG_DISPLAY);
607             serializer.attribute(null, ATTR_UNIQUE_ID, uniqueId);
608             state.saveToXml(serializer);
609             serializer.endTag(null, TAG_DISPLAY);
610         }
611         serializer.endTag(null, TAG_DISPLAY_STATES);
612         serializer.startTag(null, TAG_STABLE_DEVICE_VALUES);
613         mStableDeviceValues.saveToXml(serializer);
614         serializer.endTag(null, TAG_STABLE_DEVICE_VALUES);
615         serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
616         mGlobalBrightnessConfigurations.saveToXml(serializer);
617         serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
618         serializer.startTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
619         serializer.text(Float.toString(mBrightnessNitsForDefaultDisplay));
620         serializer.endTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
621         serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE);
622         serializer.endDocument();
623     }
624 
dump(PrintWriter pw)625     public void dump(PrintWriter pw) {
626         pw.println("PersistentDataStore");
627         pw.println("  mLoaded=" + mLoaded);
628         pw.println("  mDirty=" + mDirty);
629         pw.println("  RememberedWifiDisplays:");
630         int i = 0;
631         for (WifiDisplay display : mRememberedWifiDisplays) {
632             pw.println("    " + i++ + ": " + display);
633         }
634         pw.println("  DisplayStates:");
635         i = 0;
636         for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
637             pw.println("    " + i++ + ": " + entry.getKey());
638             entry.getValue().dump(pw, "      ");
639         }
640         pw.println("  StableDeviceValues:");
641         mStableDeviceValues.dump(pw, "      ");
642         pw.println("  GlobalBrightnessConfigurations:");
643         mGlobalBrightnessConfigurations.dump(pw, "      ");
644         pw.println("  mBrightnessNitsForDefaultDisplay=" + mBrightnessNitsForDefaultDisplay);
645     }
646 
647     private static final class DisplayState {
648         private int mColorMode;
649         private float mBrightness = Float.NaN;
650         private int mWidth;
651         private int mHeight;
652         private float mRefreshRate;
653 
654         // Brightness configuration by user
655         private BrightnessConfigurations mDisplayBrightnessConfigurations =
656                 new BrightnessConfigurations();
657 
setColorMode(int colorMode)658         public boolean setColorMode(int colorMode) {
659             if (colorMode == mColorMode) {
660                 return false;
661             }
662             mColorMode = colorMode;
663             return true;
664         }
665 
getColorMode()666         public int getColorMode() {
667             return mColorMode;
668         }
669 
setBrightness(float brightness)670         public boolean setBrightness(float brightness) {
671             if (brightness == mBrightness) {
672                 return false;
673             }
674             mBrightness = brightness;
675             return true;
676         }
677 
getBrightness()678         public float getBrightness() {
679             return mBrightness;
680         }
681 
setBrightnessConfiguration(BrightnessConfiguration configuration, int userSerial, String packageName)682         public boolean setBrightnessConfiguration(BrightnessConfiguration configuration,
683                 int userSerial, String packageName) {
684             mDisplayBrightnessConfigurations.setBrightnessConfigurationForUser(
685                     configuration, userSerial, packageName);
686             return true;
687         }
688 
getBrightnessConfiguration(int userSerial)689         public BrightnessConfiguration getBrightnessConfiguration(int userSerial) {
690             return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial);
691         }
692 
setResolution(int width, int height)693         public boolean setResolution(int width, int height) {
694             if (width == mWidth && height == mHeight) {
695                 return false;
696             }
697             mWidth = width;
698             mHeight = height;
699             return true;
700         }
701 
getResolution()702         public Point getResolution() {
703             return new Point(mWidth, mHeight);
704         }
705 
setRefreshRate(float refreshRate)706         public boolean setRefreshRate(float refreshRate) {
707             if (refreshRate == mRefreshRate) {
708                 return false;
709             }
710             mRefreshRate = refreshRate;
711             return true;
712         }
713 
getRefreshRate()714         public float getRefreshRate() {
715             return mRefreshRate;
716         }
717 
loadFromXml(TypedXmlPullParser parser)718         public void loadFromXml(TypedXmlPullParser parser)
719                 throws IOException, XmlPullParserException {
720             final int outerDepth = parser.getDepth();
721 
722             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
723                 switch (parser.getName()) {
724                     case TAG_COLOR_MODE:
725                         String value = parser.nextText();
726                         mColorMode = Integer.parseInt(value);
727                         break;
728                     case TAG_BRIGHTNESS_VALUE:
729                         String brightness = parser.nextText();
730                         try {
731                             mBrightness = Float.parseFloat(brightness);
732                         } catch (NumberFormatException e) {
733                             mBrightness = Float.NaN;
734                         }
735                         break;
736                     case TAG_BRIGHTNESS_CONFIGURATIONS:
737                         mDisplayBrightnessConfigurations.loadFromXml(parser);
738                         break;
739                     case TAG_RESOLUTION_WIDTH:
740                         String width = parser.nextText();
741                         mWidth = Integer.parseInt(width);
742                         break;
743                     case TAG_RESOLUTION_HEIGHT:
744                         String height = parser.nextText();
745                         mHeight = Integer.parseInt(height);
746                         break;
747                     case TAG_REFRESH_RATE:
748                         String refreshRate = parser.nextText();
749                         mRefreshRate = Float.parseFloat(refreshRate);
750                         break;
751                 }
752             }
753         }
754 
saveToXml(TypedXmlSerializer serializer)755         public void saveToXml(TypedXmlSerializer serializer) throws IOException {
756             serializer.startTag(null, TAG_COLOR_MODE);
757             serializer.text(Integer.toString(mColorMode));
758             serializer.endTag(null, TAG_COLOR_MODE);
759 
760             serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
761             if (!Float.isNaN(mBrightness)) {
762                 serializer.text(Float.toString(mBrightness));
763             }
764             serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
765 
766             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
767             mDisplayBrightnessConfigurations.saveToXml(serializer);
768             serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
769 
770             serializer.startTag(null, TAG_RESOLUTION_WIDTH);
771             serializer.text(Integer.toString(mWidth));
772             serializer.endTag(null, TAG_RESOLUTION_WIDTH);
773 
774             serializer.startTag(null, TAG_RESOLUTION_HEIGHT);
775             serializer.text(Integer.toString(mHeight));
776             serializer.endTag(null, TAG_RESOLUTION_HEIGHT);
777 
778             serializer.startTag(null, TAG_REFRESH_RATE);
779             serializer.text(Float.toString(mRefreshRate));
780             serializer.endTag(null, TAG_REFRESH_RATE);
781         }
782 
dump(final PrintWriter pw, final String prefix)783         public void dump(final PrintWriter pw, final String prefix) {
784             pw.println(prefix + "ColorMode=" + mColorMode);
785             pw.println(prefix + "BrightnessValue=" + mBrightness);
786             pw.println(prefix + "DisplayBrightnessConfigurations: ");
787             mDisplayBrightnessConfigurations.dump(pw, prefix);
788             pw.println(prefix + "Resolution=" + mWidth + " " + mHeight);
789             pw.println(prefix + "RefreshRate=" + mRefreshRate);
790         }
791     }
792 
793     private static final class StableDeviceValues {
794         private int mWidth;
795         private int mHeight;
796 
getDisplaySize()797         private Point getDisplaySize() {
798             return new Point(mWidth, mHeight);
799         }
800 
setDisplaySize(Point r)801         public boolean setDisplaySize(Point r) {
802             if (mWidth != r.x || mHeight != r.y) {
803                 mWidth = r.x;
804                 mHeight = r.y;
805                 return true;
806             }
807             return false;
808         }
809 
loadFromXml(TypedXmlPullParser parser)810         public void loadFromXml(TypedXmlPullParser parser)
811                 throws IOException, XmlPullParserException {
812             final int outerDepth = parser.getDepth();
813             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
814                 switch (parser.getName()) {
815                     case TAG_STABLE_DISPLAY_WIDTH:
816                         mWidth = loadIntValue(parser);
817                         break;
818                     case TAG_STABLE_DISPLAY_HEIGHT:
819                         mHeight = loadIntValue(parser);
820                         break;
821                 }
822             }
823         }
824 
loadIntValue(TypedXmlPullParser parser)825         private static int loadIntValue(TypedXmlPullParser parser)
826             throws IOException, XmlPullParserException {
827             try {
828                 String value = parser.nextText();
829                 return Integer.parseInt(value);
830             } catch (NumberFormatException nfe) {
831                 return 0;
832             }
833         }
834 
saveToXml(TypedXmlSerializer serializer)835         public void saveToXml(TypedXmlSerializer serializer) throws IOException {
836             if (mWidth > 0 && mHeight > 0) {
837                 serializer.startTag(null, TAG_STABLE_DISPLAY_WIDTH);
838                 serializer.text(Integer.toString(mWidth));
839                 serializer.endTag(null, TAG_STABLE_DISPLAY_WIDTH);
840                 serializer.startTag(null, TAG_STABLE_DISPLAY_HEIGHT);
841                 serializer.text(Integer.toString(mHeight));
842                 serializer.endTag(null, TAG_STABLE_DISPLAY_HEIGHT);
843             }
844         }
845 
dump(final PrintWriter pw, final String prefix)846         public void dump(final PrintWriter pw, final String prefix) {
847             pw.println(prefix + "StableDisplayWidth=" + mWidth);
848             pw.println(prefix + "StableDisplayHeight=" + mHeight);
849         }
850     }
851 
852     private static final class BrightnessConfigurations {
853         // Maps from a user ID to the users' given brightness configuration
854         private final SparseArray<BrightnessConfiguration> mConfigurations;
855         // Timestamp of time the configuration was set.
856         private final SparseLongArray mTimeStamps;
857         // Package that set the configuration.
858         private final SparseArray<String> mPackageNames;
859 
BrightnessConfigurations()860         public BrightnessConfigurations() {
861             mConfigurations = new SparseArray<>();
862             mTimeStamps = new SparseLongArray();
863             mPackageNames = new SparseArray<>();
864         }
865 
setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, String packageName)866         private boolean setBrightnessConfigurationForUser(BrightnessConfiguration c,
867                 int userSerial, String packageName) {
868             BrightnessConfiguration currentConfig = mConfigurations.get(userSerial);
869             if (currentConfig != c && (currentConfig == null || !currentConfig.equals(c))) {
870                 if (c != null) {
871                     if (packageName == null) {
872                         mPackageNames.remove(userSerial);
873                     } else {
874                         mPackageNames.put(userSerial, packageName);
875                     }
876                     mTimeStamps.put(userSerial, System.currentTimeMillis());
877                     mConfigurations.put(userSerial, c);
878                 } else {
879                     mPackageNames.remove(userSerial);
880                     mTimeStamps.delete(userSerial);
881                     mConfigurations.remove(userSerial);
882                 }
883                 return true;
884             }
885             return false;
886         }
887 
getBrightnessConfiguration(int userSerial)888         public BrightnessConfiguration getBrightnessConfiguration(int userSerial) {
889             return mConfigurations.get(userSerial);
890         }
891 
loadFromXml(TypedXmlPullParser parser)892         public void loadFromXml(TypedXmlPullParser parser)
893                 throws IOException, XmlPullParserException {
894             final int outerDepth = parser.getDepth();
895             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
896                 if (TAG_BRIGHTNESS_CONFIGURATION.equals(parser.getName())) {
897                     int userSerial;
898                     try {
899                         userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL);
900                     } catch (NumberFormatException nfe) {
901                         userSerial = -1;
902                         Slog.e(TAG, "Failed to read in brightness configuration", nfe);
903                     }
904 
905                     String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
906                     long timeStamp = parser.getAttributeLong(null, ATTR_TIME_STAMP, -1);
907 
908                     try {
909                         BrightnessConfiguration config =
910                                 BrightnessConfiguration.loadFromXml(parser);
911                         if (userSerial >= 0 && config != null) {
912                             mConfigurations.put(userSerial, config);
913                             if (timeStamp != -1) {
914                                 mTimeStamps.put(userSerial, timeStamp);
915                             }
916                             if (packageName != null) {
917                                 mPackageNames.put(userSerial, packageName);
918                             }
919                         }
920                     } catch (IllegalArgumentException iae) {
921                         Slog.e(TAG, "Failed to load brightness configuration!", iae);
922                     }
923                 }
924             }
925         }
926 
saveToXml(TypedXmlSerializer serializer)927         public void saveToXml(TypedXmlSerializer serializer) throws IOException {
928             for (int i = 0; i < mConfigurations.size(); i++) {
929                 final int userSerial = mConfigurations.keyAt(i);
930                 final BrightnessConfiguration config = mConfigurations.valueAt(i);
931 
932                 serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATION);
933                 serializer.attributeInt(null, ATTR_USER_SERIAL, userSerial);
934                 String packageName = mPackageNames.get(userSerial);
935                 if (packageName != null) {
936                     serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
937                 }
938                 long timestamp = mTimeStamps.get(userSerial, -1);
939                 if (timestamp != -1) {
940                     serializer.attributeLong(null, ATTR_TIME_STAMP, timestamp);
941                 }
942                 config.saveToXml(serializer);
943                 serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATION);
944             }
945         }
946 
dump(final PrintWriter pw, final String prefix)947         public void dump(final PrintWriter pw, final String prefix) {
948             for (int i = 0; i < mConfigurations.size(); i++) {
949                 final int userSerial = mConfigurations.keyAt(i);
950                 long time = mTimeStamps.get(userSerial, -1);
951                 String packageName = mPackageNames.get(userSerial);
952                 pw.println(prefix + "User " + userSerial + ":");
953                 if (time != -1) {
954                     pw.println(prefix + "  set at: " + TimeUtils.formatForLogging(time));
955                 }
956                 if (packageName != null) {
957                     pw.println(prefix + "  set by: " + packageName);
958                 }
959                 pw.println(prefix + "  " + mConfigurations.valueAt(i));
960             }
961         }
962     }
963 
964     @VisibleForTesting
965     static class Injector {
966         private final AtomicFile mAtomicFile;
967 
Injector()968         public Injector() {
969             mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"),
970                     "display-state");
971         }
972 
openRead()973         public InputStream openRead() throws FileNotFoundException {
974             return mAtomicFile.openRead();
975         }
976 
startWrite()977         public OutputStream startWrite() throws IOException {
978             return mAtomicFile.startWrite();
979         }
980 
finishWrite(OutputStream os, boolean success)981         public void finishWrite(OutputStream os, boolean success) {
982             if (!(os instanceof FileOutputStream)) {
983                 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os);
984             }
985             FileOutputStream fos = (FileOutputStream) os;
986             if (success) {
987                 mAtomicFile.finishWrite(fos);
988             } else {
989                 mAtomicFile.failWrite(fos);
990             }
991         }
992     }
993 }
994