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