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