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
17package com.android.server.display;
18
19import com.android.internal.util.FastXmlSerializer;
20import com.android.internal.util.XmlUtils;
21
22import org.xmlpull.v1.XmlPullParser;
23import org.xmlpull.v1.XmlPullParserException;
24import org.xmlpull.v1.XmlSerializer;
25
26import android.graphics.Point;
27import android.hardware.display.WifiDisplay;
28import android.util.AtomicFile;
29import android.util.Slog;
30import android.util.Xml;
31import android.view.Display;
32
33import java.io.BufferedInputStream;
34import java.io.BufferedOutputStream;
35import java.io.File;
36import java.io.FileNotFoundException;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.PrintWriter;
41import java.nio.charset.StandardCharsets;
42import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.Map;
45
46import libcore.io.IoUtils;
47import libcore.util.Objects;
48
49/**
50 * Manages persistent state recorded by the display manager service as an XML file.
51 * Caller must acquire lock on the data store before accessing it.
52 *
53 * File format:
54 * <code>
55 * &lt;display-manager-state>
56 *   &lt;remembered-wifi-displays>
57 *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
58 *   &lt;remembered-wifi-displays>
59 *   &lt;display-states>
60 *      &lt;display>
61 *          &lt;color-mode>0&lt;/color-mode>
62 *      &lt;/display>
63 *  &lt;/display-states>
64 *  &lt;stable-device-values>
65 *      &lt;stable-display-height>1920&lt;stable-display-height>
66 *      &lt;stable-display-width>1080&lt;stable-display-width>
67 *  &lt;/stable-device-values>
68 * &lt;/display-manager-state>
69 * </code>
70 *
71 * TODO: refactor this to extract common code shared with the input manager's data store
72 */
73final class PersistentDataStore {
74    static final String TAG = "DisplayManager";
75
76    // Remembered Wifi display devices.
77    private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
78
79    // Display state by unique id.
80    private final HashMap<String, DisplayState> mDisplayStates =
81            new HashMap<String, DisplayState>();
82
83    // Display values which should be stable across the device's lifetime.
84    private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
85
86    // The atomic file used to safely read or write the file.
87    private final AtomicFile mAtomicFile;
88
89    // True if the data has been loaded.
90    private boolean mLoaded;
91
92    // True if there are changes to be saved.
93    private boolean mDirty;
94
95    public PersistentDataStore() {
96        mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
97    }
98
99    public void saveIfNeeded() {
100        if (mDirty) {
101            save();
102            mDirty = false;
103        }
104    }
105
106    public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
107        loadIfNeeded();
108        int index = findRememberedWifiDisplay(deviceAddress);
109        if (index >= 0) {
110            return mRememberedWifiDisplays.get(index);
111        }
112        return null;
113    }
114
115    public WifiDisplay[] getRememberedWifiDisplays() {
116        loadIfNeeded();
117        return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
118    }
119
120    public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
121        if (display != null) {
122            loadIfNeeded();
123
124            String alias = null;
125            int index = findRememberedWifiDisplay(display.getDeviceAddress());
126            if (index >= 0) {
127                alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
128            }
129            if (!Objects.equal(display.getDeviceAlias(), alias)) {
130                return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
131                        alias, display.isAvailable(), display.canConnect(), display.isRemembered());
132            }
133        }
134        return display;
135    }
136
137    public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
138        WifiDisplay[] results = displays;
139        if (results != null) {
140            int count = displays.length;
141            for (int i = 0; i < count; i++) {
142                WifiDisplay result = applyWifiDisplayAlias(displays[i]);
143                if (result != displays[i]) {
144                    if (results == displays) {
145                        results = new WifiDisplay[count];
146                        System.arraycopy(displays, 0, results, 0, count);
147                    }
148                    results[i] = result;
149                }
150            }
151        }
152        return results;
153    }
154
155    public boolean rememberWifiDisplay(WifiDisplay display) {
156        loadIfNeeded();
157
158        int index = findRememberedWifiDisplay(display.getDeviceAddress());
159        if (index >= 0) {
160            WifiDisplay other = mRememberedWifiDisplays.get(index);
161            if (other.equals(display)) {
162                return false; // already remembered without change
163            }
164            mRememberedWifiDisplays.set(index, display);
165        } else {
166            mRememberedWifiDisplays.add(display);
167        }
168        setDirty();
169        return true;
170    }
171
172    public boolean forgetWifiDisplay(String deviceAddress) {
173		loadIfNeeded();
174        int index = findRememberedWifiDisplay(deviceAddress);
175        if (index >= 0) {
176            mRememberedWifiDisplays.remove(index);
177            setDirty();
178            return true;
179        }
180        return false;
181    }
182
183    private int findRememberedWifiDisplay(String deviceAddress) {
184        int count = mRememberedWifiDisplays.size();
185        for (int i = 0; i < count; i++) {
186            if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
187                return i;
188            }
189        }
190        return -1;
191    }
192
193    public int getColorMode(DisplayDevice device) {
194        if (!device.hasStableUniqueId()) {
195            return Display.COLOR_MODE_INVALID;
196        }
197        DisplayState state = getDisplayState(device.getUniqueId(), false);
198        if (state == null) {
199            return Display.COLOR_MODE_INVALID;
200        }
201        return state.getColorMode();
202    }
203
204    public boolean setColorMode(DisplayDevice device, int colorMode) {
205        if (!device.hasStableUniqueId()) {
206            return false;
207        }
208        DisplayState state = getDisplayState(device.getUniqueId(), true);
209        if (state.setColorMode(colorMode)) {
210            setDirty();
211            return true;
212        }
213        return false;
214    }
215
216	public Point getStableDisplaySize() {
217		loadIfNeeded();
218		return mStableDeviceValues.getDisplaySize();
219	}
220
221	public void setStableDisplaySize(Point size) {
222		loadIfNeeded();
223		if (mStableDeviceValues.setDisplaySize(size)) {
224			setDirty();
225		}
226	}
227
228    private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
229        loadIfNeeded();
230        DisplayState state = mDisplayStates.get(uniqueId);
231        if (state == null && createIfAbsent) {
232            state = new DisplayState();
233            mDisplayStates.put(uniqueId, state);
234            setDirty();
235        }
236        return state;
237    }
238
239    public void loadIfNeeded() {
240        if (!mLoaded) {
241            load();
242            mLoaded = true;
243        }
244    }
245
246    private void setDirty() {
247        mDirty = true;
248    }
249
250    private void clearState() {
251        mRememberedWifiDisplays.clear();
252    }
253
254    private void load() {
255        clearState();
256
257        final InputStream is;
258        try {
259            is = mAtomicFile.openRead();
260        } catch (FileNotFoundException ex) {
261            return;
262        }
263
264        XmlPullParser parser;
265        try {
266            parser = Xml.newPullParser();
267            parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name());
268            loadFromXml(parser);
269        } catch (IOException ex) {
270            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
271            clearState();
272        } catch (XmlPullParserException ex) {
273            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
274            clearState();
275        } finally {
276            IoUtils.closeQuietly(is);
277        }
278    }
279
280    private void save() {
281        final FileOutputStream os;
282        try {
283            os = mAtomicFile.startWrite();
284            boolean success = false;
285            try {
286                XmlSerializer serializer = new FastXmlSerializer();
287                serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name());
288                saveToXml(serializer);
289                serializer.flush();
290                success = true;
291            } finally {
292                if (success) {
293                    mAtomicFile.finishWrite(os);
294                } else {
295                    mAtomicFile.failWrite(os);
296                }
297            }
298        } catch (IOException ex) {
299            Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
300        }
301    }
302
303    private void loadFromXml(XmlPullParser parser)
304            throws IOException, XmlPullParserException {
305        XmlUtils.beginDocument(parser, "display-manager-state");
306        final int outerDepth = parser.getDepth();
307        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
308            if (parser.getName().equals("remembered-wifi-displays")) {
309                loadRememberedWifiDisplaysFromXml(parser);
310            }
311            if (parser.getName().equals("display-states")) {
312                loadDisplaysFromXml(parser);
313            }
314            if (parser.getName().equals("stable-device-values")) {
315                mStableDeviceValues.loadFromXml(parser);
316            }
317        }
318    }
319
320    private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
321            throws IOException, XmlPullParserException {
322        final int outerDepth = parser.getDepth();
323        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
324            if (parser.getName().equals("wifi-display")) {
325                String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
326                String deviceName = parser.getAttributeValue(null, "deviceName");
327                String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
328                if (deviceAddress == null || deviceName == null) {
329                    throw new XmlPullParserException(
330                            "Missing deviceAddress or deviceName attribute on wifi-display.");
331                }
332                if (findRememberedWifiDisplay(deviceAddress) >= 0) {
333                    throw new XmlPullParserException(
334                            "Found duplicate wifi display device address.");
335                }
336
337                mRememberedWifiDisplays.add(
338                        new WifiDisplay(deviceAddress, deviceName, deviceAlias,
339                                false, false, false));
340            }
341        }
342    }
343
344    private void loadDisplaysFromXml(XmlPullParser parser)
345            throws IOException, XmlPullParserException {
346        final int outerDepth = parser.getDepth();
347        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
348            if (parser.getName().equals("display")) {
349                String uniqueId = parser.getAttributeValue(null, "unique-id");
350                if (uniqueId == null) {
351                    throw new XmlPullParserException(
352                            "Missing unique-id attribute on display.");
353                }
354                if (mDisplayStates.containsKey(uniqueId)) {
355                    throw new XmlPullParserException("Found duplicate display.");
356                }
357
358                DisplayState state = new DisplayState();
359                state.loadFromXml(parser);
360                mDisplayStates.put(uniqueId, state);
361            }
362        }
363    }
364
365    private void saveToXml(XmlSerializer serializer) throws IOException {
366        serializer.startDocument(null, true);
367        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
368        serializer.startTag(null, "display-manager-state");
369        serializer.startTag(null, "remembered-wifi-displays");
370        for (WifiDisplay display : mRememberedWifiDisplays) {
371            serializer.startTag(null, "wifi-display");
372            serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
373            serializer.attribute(null, "deviceName", display.getDeviceName());
374            if (display.getDeviceAlias() != null) {
375                serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
376            }
377            serializer.endTag(null, "wifi-display");
378        }
379        serializer.endTag(null, "remembered-wifi-displays");
380        serializer.startTag(null, "display-states");
381        for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
382            final String uniqueId = entry.getKey();
383            final DisplayState state = entry.getValue();
384            serializer.startTag(null, "display");
385            serializer.attribute(null, "unique-id", uniqueId);
386            state.saveToXml(serializer);
387            serializer.endTag(null, "display");
388        }
389        serializer.endTag(null, "display-states");
390        serializer.startTag(null, "stable-device-values");
391        mStableDeviceValues.saveToXml(serializer);
392        serializer.endTag(null, "stable-device-values");
393        serializer.endTag(null, "display-manager-state");
394        serializer.endDocument();
395    }
396
397    public void dump(PrintWriter pw) {
398        pw.println("PersistentDataStore");
399        pw.println("  mLoaded=" + mLoaded);
400        pw.println("  mDirty=" + mDirty);
401        pw.println("  RememberedWifiDisplays:");
402        int i = 0;
403        for (WifiDisplay display : mRememberedWifiDisplays) {
404            pw.println("    " + i++ + ": " + display);
405        }
406        pw.println("  DisplayStates:");
407        i = 0;
408        for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
409            pw.println("    " + i++ + ": " + entry.getKey());
410            entry.getValue().dump(pw, "      ");
411        }
412        pw.println("  StableDeviceValues:");
413        mStableDeviceValues.dump(pw, "      ");
414    }
415
416    private static final class DisplayState {
417        private int mColorMode;
418
419        public boolean setColorMode(int colorMode) {
420            if (colorMode == mColorMode) {
421                return false;
422            }
423            mColorMode = colorMode;
424            return true;
425        }
426
427        public int getColorMode() {
428            return mColorMode;
429        }
430
431        public void loadFromXml(XmlPullParser parser)
432                throws IOException, XmlPullParserException {
433            final int outerDepth = parser.getDepth();
434
435            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
436                if (parser.getName().equals("color-mode")) {
437                    String value = parser.nextText();
438                    mColorMode = Integer.parseInt(value);
439                }
440            }
441        }
442
443        public void saveToXml(XmlSerializer serializer) throws IOException {
444            serializer.startTag(null, "color-mode");
445            serializer.text(Integer.toString(mColorMode));
446            serializer.endTag(null, "color-mode");
447        }
448
449        public void dump(final PrintWriter pw, final String prefix) {
450            pw.println(prefix + "ColorMode=" + mColorMode);
451        }
452    }
453
454    private static final class StableDeviceValues {
455        private int mWidth;
456        private int mHeight;
457
458        private Point getDisplaySize() {
459            return new Point(mWidth, mHeight);
460        }
461
462        public boolean setDisplaySize(Point r) {
463            if (mWidth != r.x || mHeight != r.y) {
464                mWidth = r.x;
465                mHeight = r.y;
466                return true;
467            }
468            return false;
469        }
470
471        public void loadFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
472            final int outerDepth = parser.getDepth();
473            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
474                switch (parser.getName()) {
475                    case "stable-display-width":
476                        mWidth = loadIntValue(parser);
477                        break;
478                    case "stable-display-height":
479                        mHeight = loadIntValue(parser);
480                        break;
481                }
482            }
483        }
484
485        private static int loadIntValue(XmlPullParser parser)
486            throws IOException, XmlPullParserException {
487            try {
488                String value = parser.nextText();
489                return Integer.parseInt(value);
490            } catch (NumberFormatException nfe) {
491                return 0;
492            }
493        }
494
495        public void saveToXml(XmlSerializer serializer) throws IOException {
496            if (mWidth > 0 && mHeight > 0) {
497                serializer.startTag(null, "stable-display-width");
498                serializer.text(Integer.toString(mWidth));
499                serializer.endTag(null, "stable-display-width");
500                serializer.startTag(null, "stable-display-height");
501                serializer.text(Integer.toString(mHeight));
502                serializer.endTag(null, "stable-display-height");
503            }
504        }
505
506        public void dump(final PrintWriter pw, final String prefix) {
507            pw.println(prefix + "StableDisplayWidth=" + mWidth);
508            pw.println(prefix + "StableDisplayHeight=" + mHeight);
509        }
510    }
511}
512