PersistentDataStore.java revision 89d5546d7fd3a3bb19820c42e8b4527013dd6545
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[] getRememberedWifiDisplays() {
85        loadIfNeeded();
86        return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
87    }
88
89    public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
90        if (display != null) {
91            loadIfNeeded();
92
93            int index = findRememberedWifiDisplay(display.getDeviceAddress());
94            if (index >= 0) {
95                return mRememberedWifiDisplays.get(index);
96            }
97        }
98        return display;
99    }
100
101    public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
102        WifiDisplay[] results = displays;
103        if (results != null) {
104            int count = displays.length;
105            for (int i = 0; i < count; i++) {
106                WifiDisplay result = applyWifiDisplayAlias(displays[i]);
107                if (result != displays[i]) {
108                    if (results == displays) {
109                        results = new WifiDisplay[count];
110                        System.arraycopy(displays, 0, results, 0, count);
111                    }
112                    results[i] = result;
113                }
114            }
115        }
116        return results;
117    }
118
119    public boolean rememberWifiDisplay(WifiDisplay display) {
120        loadIfNeeded();
121
122        int index = findRememberedWifiDisplay(display.getDeviceAddress());
123        if (index >= 0) {
124            WifiDisplay other = mRememberedWifiDisplays.get(index);
125            if (other.equals(display)) {
126                return false; // already remembered without change
127            }
128            mRememberedWifiDisplays.set(index, display);
129        } else {
130            mRememberedWifiDisplays.add(display);
131        }
132        setDirty();
133        return true;
134    }
135
136    public boolean renameWifiDisplay(String deviceAddress, String alias) {
137        int index = findRememberedWifiDisplay(deviceAddress);
138        if (index >= 0) {
139            WifiDisplay display = mRememberedWifiDisplays.get(index);
140            if (Objects.equal(display.getDeviceAlias(), alias)) {
141                return false; // already has this alias
142            }
143            WifiDisplay renamedDisplay = new WifiDisplay(deviceAddress,
144                    display.getDeviceName(), alias);
145            mRememberedWifiDisplays.set(index, renamedDisplay);
146            setDirty();
147            return true;
148        }
149        return false;
150    }
151
152    public boolean forgetWifiDisplay(String deviceAddress) {
153        int index = findRememberedWifiDisplay(deviceAddress);
154        if (index >= 0) {
155            mRememberedWifiDisplays.remove(index);
156            setDirty();
157            return true;
158        }
159        return false;
160    }
161
162    private int findRememberedWifiDisplay(String deviceAddress) {
163        int count = mRememberedWifiDisplays.size();
164        for (int i = 0; i < count; i++) {
165            if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
166                return i;
167            }
168        }
169        return -1;
170    }
171
172    private void loadIfNeeded() {
173        if (!mLoaded) {
174            load();
175            mLoaded = true;
176        }
177    }
178
179    private void setDirty() {
180        mDirty = true;
181    }
182
183    private void clearState() {
184        mRememberedWifiDisplays.clear();
185    }
186
187    private void load() {
188        clearState();
189
190        final InputStream is;
191        try {
192            is = mAtomicFile.openRead();
193        } catch (FileNotFoundException ex) {
194            return;
195        }
196
197        XmlPullParser parser;
198        try {
199            parser = Xml.newPullParser();
200            parser.setInput(new BufferedInputStream(is), null);
201            loadFromXml(parser);
202        } catch (IOException ex) {
203            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
204            clearState();
205        } catch (XmlPullParserException ex) {
206            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
207            clearState();
208        } finally {
209            IoUtils.closeQuietly(is);
210        }
211    }
212
213    private void save() {
214        final FileOutputStream os;
215        try {
216            os = mAtomicFile.startWrite();
217            boolean success = false;
218            try {
219                XmlSerializer serializer = new FastXmlSerializer();
220                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
221                saveToXml(serializer);
222                serializer.flush();
223                success = true;
224            } finally {
225                if (success) {
226                    mAtomicFile.finishWrite(os);
227                } else {
228                    mAtomicFile.failWrite(os);
229                }
230            }
231        } catch (IOException ex) {
232            Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
233        }
234    }
235
236    private void loadFromXml(XmlPullParser parser)
237            throws IOException, XmlPullParserException {
238        XmlUtils.beginDocument(parser, "display-manager-state");
239        final int outerDepth = parser.getDepth();
240        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
241            if (parser.getName().equals("remembered-wifi-displays")) {
242                loadRememberedWifiDisplaysFromXml(parser);
243            }
244        }
245    }
246
247    private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
248            throws IOException, XmlPullParserException {
249        final int outerDepth = parser.getDepth();
250        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
251            if (parser.getName().equals("wifi-display")) {
252                String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
253                String deviceName = parser.getAttributeValue(null, "deviceName");
254                String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
255                if (deviceAddress == null || deviceName == null) {
256                    throw new XmlPullParserException(
257                            "Missing deviceAddress or deviceName attribute on wifi-display.");
258                }
259                if (findRememberedWifiDisplay(deviceAddress) >= 0) {
260                    throw new XmlPullParserException(
261                            "Found duplicate wifi display device address.");
262                }
263
264                mRememberedWifiDisplays.add(
265                        new WifiDisplay(deviceAddress, deviceName, deviceAlias));
266            }
267        }
268    }
269
270    private void saveToXml(XmlSerializer serializer) throws IOException {
271        serializer.startDocument(null, true);
272        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
273        serializer.startTag(null, "display-manager-state");
274        serializer.startTag(null, "remembered-wifi-displays");
275        for (WifiDisplay display : mRememberedWifiDisplays) {
276            serializer.startTag(null, "wifi-display");
277            serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
278            serializer.attribute(null, "deviceName", display.getDeviceName());
279            if (display.getDeviceAlias() != null) {
280                serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
281            }
282            serializer.endTag(null, "wifi-display");
283        }
284        serializer.endTag(null, "remembered-wifi-displays");
285        serializer.endTag(null, "display-manager-state");
286        serializer.endDocument();
287    }
288}