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 com.android.internal.util.FastXmlSerializer;
20 import com.android.internal.util.XmlUtils;
21 
22 import org.xmlpull.v1.XmlPullParser;
23 import org.xmlpull.v1.XmlPullParserException;
24 import org.xmlpull.v1.XmlSerializer;
25 
26 import android.hardware.display.WifiDisplay;
27 import android.util.AtomicFile;
28 import android.util.Slog;
29 import android.util.Xml;
30 
31 import java.io.BufferedInputStream;
32 import java.io.BufferedOutputStream;
33 import java.io.File;
34 import java.io.FileNotFoundException;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.nio.charset.StandardCharsets;
39 import java.util.ArrayList;
40 
41 import libcore.io.IoUtils;
42 import 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  */
59 final 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 
PersistentDataStore()74     public PersistentDataStore() {
75         mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
76     }
77 
saveIfNeeded()78     public void saveIfNeeded() {
79         if (mDirty) {
80             save();
81             mDirty = false;
82         }
83     }
84 
getRememberedWifiDisplay(String deviceAddress)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 
getRememberedWifiDisplays()94     public WifiDisplay[] getRememberedWifiDisplays() {
95         loadIfNeeded();
96         return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
97     }
98 
applyWifiDisplayAlias(WifiDisplay display)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 
applyWifiDisplayAliases(WifiDisplay[] displays)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 
rememberWifiDisplay(WifiDisplay display)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 
forgetWifiDisplay(String deviceAddress)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 
findRememberedWifiDisplay(String deviceAddress)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 
loadIfNeeded()171     private void loadIfNeeded() {
172         if (!mLoaded) {
173             load();
174             mLoaded = true;
175         }
176     }
177 
setDirty()178     private void setDirty() {
179         mDirty = true;
180     }
181 
clearState()182     private void clearState() {
183         mRememberedWifiDisplays.clear();
184     }
185 
load()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 
save()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 
loadFromXml(XmlPullParser parser)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 
loadRememberedWifiDisplaysFromXml(XmlPullParser parser)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 
saveToXml(XmlSerializer serializer)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