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