PersistentDataStore.java revision 9158825f9c41869689d6b1786d7c7aa8bdd524ce
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.hardware.display.WifiDisplay;
27import android.util.AtomicFile;
28import android.util.Slog;
29import android.util.Xml;
30
31import java.io.BufferedInputStream;
32import java.io.BufferedOutputStream;
33import java.io.File;
34import java.io.FileNotFoundException;
35import java.io.FileOutputStream;
36import java.io.IOException;
37import java.io.InputStream;
38import java.util.ArrayList;
39
40import libcore.io.IoUtils;
41import libcore.util.Objects;
42
43/**
44 * Manages persistent state recorded by the display manager service as an XML file.
45 * Caller must acquire lock on the data store before accessing it.
46 *
47 * File format:
48 * <code>
49 * &lt;display-manager-state>
50 *   &lt;remembered-wifi-displays>
51 *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
52 *   &gt;remembered-wifi-displays>
53 * &gt;/display-manager-state>
54 * </code>
55 *
56 * TODO: refactor this to extract common code shared with the input manager's data store
57 */
58final class PersistentDataStore {
59    static final String TAG = "DisplayManager";
60
61    // Remembered Wifi display devices.
62    private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
63
64    // The atomic file used to safely read or write the file.
65    private final AtomicFile mAtomicFile;
66
67    // True if the data has been loaded.
68    private boolean mLoaded;
69
70    // True if there are changes to be saved.
71    private boolean mDirty;
72
73    public PersistentDataStore() {
74        mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
75    }
76
77    public void saveIfNeeded() {
78        if (mDirty) {
79            save();
80            mDirty = false;
81        }
82    }
83
84    public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
85        loadIfNeeded();
86        int index = findRememberedWifiDisplay(deviceAddress);
87        if (index >= 0) {
88            return mRememberedWifiDisplays.get(index);
89        }
90        return null;
91    }
92
93    public WifiDisplay[] getRememberedWifiDisplays() {
94        loadIfNeeded();
95        return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
96    }
97
98    public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
99        if (display != null) {
100            loadIfNeeded();
101
102            String alias = null;
103            int index = findRememberedWifiDisplay(display.getDeviceAddress());
104            if (index >= 0) {
105                alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
106            }
107            if (!Objects.equal(display.getDeviceAlias(), alias)) {
108                return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
109                        alias, display.isAvailable(), display.canConnect(), display.isRemembered());
110            }
111        }
112        return display;
113    }
114
115    public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
116        WifiDisplay[] results = displays;
117        if (results != null) {
118            int count = displays.length;
119            for (int i = 0; i < count; i++) {
120                WifiDisplay result = applyWifiDisplayAlias(displays[i]);
121                if (result != displays[i]) {
122                    if (results == displays) {
123                        results = new WifiDisplay[count];
124                        System.arraycopy(displays, 0, results, 0, count);
125                    }
126                    results[i] = result;
127                }
128            }
129        }
130        return results;
131    }
132
133    public boolean rememberWifiDisplay(WifiDisplay display) {
134        loadIfNeeded();
135
136        int index = findRememberedWifiDisplay(display.getDeviceAddress());
137        if (index >= 0) {
138            WifiDisplay other = mRememberedWifiDisplays.get(index);
139            if (other.equals(display)) {
140                return false; // already remembered without change
141            }
142            mRememberedWifiDisplays.set(index, display);
143        } else {
144            mRememberedWifiDisplays.add(display);
145        }
146        setDirty();
147        return true;
148    }
149
150    public boolean forgetWifiDisplay(String deviceAddress) {
151        int index = findRememberedWifiDisplay(deviceAddress);
152        if (index >= 0) {
153            mRememberedWifiDisplays.remove(index);
154            setDirty();
155            return true;
156        }
157        return false;
158    }
159
160    private int findRememberedWifiDisplay(String deviceAddress) {
161        int count = mRememberedWifiDisplays.size();
162        for (int i = 0; i < count; i++) {
163            if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
164                return i;
165            }
166        }
167        return -1;
168    }
169
170    private void loadIfNeeded() {
171        if (!mLoaded) {
172            load();
173            mLoaded = true;
174        }
175    }
176
177    private void setDirty() {
178        mDirty = true;
179    }
180
181    private void clearState() {
182        mRememberedWifiDisplays.clear();
183    }
184
185    private void load() {
186        clearState();
187
188        final InputStream is;
189        try {
190            is = mAtomicFile.openRead();
191        } catch (FileNotFoundException ex) {
192            return;
193        }
194
195        XmlPullParser parser;
196        try {
197            parser = Xml.newPullParser();
198            parser.setInput(new BufferedInputStream(is), null);
199            loadFromXml(parser);
200        } catch (IOException ex) {
201            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
202            clearState();
203        } catch (XmlPullParserException ex) {
204            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
205            clearState();
206        } finally {
207            IoUtils.closeQuietly(is);
208        }
209    }
210
211    private void save() {
212        final FileOutputStream os;
213        try {
214            os = mAtomicFile.startWrite();
215            boolean success = false;
216            try {
217                XmlSerializer serializer = new FastXmlSerializer();
218                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
219                saveToXml(serializer);
220                serializer.flush();
221                success = true;
222            } finally {
223                if (success) {
224                    mAtomicFile.finishWrite(os);
225                } else {
226                    mAtomicFile.failWrite(os);
227                }
228            }
229        } catch (IOException ex) {
230            Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
231        }
232    }
233
234    private void loadFromXml(XmlPullParser parser)
235            throws IOException, XmlPullParserException {
236        XmlUtils.beginDocument(parser, "display-manager-state");
237        final int outerDepth = parser.getDepth();
238        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
239            if (parser.getName().equals("remembered-wifi-displays")) {
240                loadRememberedWifiDisplaysFromXml(parser);
241            }
242        }
243    }
244
245    private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
246            throws IOException, XmlPullParserException {
247        final int outerDepth = parser.getDepth();
248        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
249            if (parser.getName().equals("wifi-display")) {
250                String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
251                String deviceName = parser.getAttributeValue(null, "deviceName");
252                String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
253                if (deviceAddress == null || deviceName == null) {
254                    throw new XmlPullParserException(
255                            "Missing deviceAddress or deviceName attribute on wifi-display.");
256                }
257                if (findRememberedWifiDisplay(deviceAddress) >= 0) {
258                    throw new XmlPullParserException(
259                            "Found duplicate wifi display device address.");
260                }
261
262                mRememberedWifiDisplays.add(
263                        new WifiDisplay(deviceAddress, deviceName, deviceAlias,
264                                false, false, false));
265            }
266        }
267    }
268
269    private void saveToXml(XmlSerializer serializer) throws IOException {
270        serializer.startDocument(null, true);
271        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
272        serializer.startTag(null, "display-manager-state");
273        serializer.startTag(null, "remembered-wifi-displays");
274        for (WifiDisplay display : mRememberedWifiDisplays) {
275            serializer.startTag(null, "wifi-display");
276            serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
277            serializer.attribute(null, "deviceName", display.getDeviceName());
278            if (display.getDeviceAlias() != null) {
279                serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
280            }
281            serializer.endTag(null, "wifi-display");
282        }
283        serializer.endTag(null, "remembered-wifi-displays");
284        serializer.endTag(null, "display-manager-state");
285        serializer.endDocument();
286    }
287}