blob: 47701b99860afe9f91d1e0c19f9c5296dc7ae887 [file] [log] [blame]
Jeff Brown89d55462012-09-19 11:33:42 -07001/*
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;
Michael Wright1c9977b2016-07-12 13:30:10 -070030import android.view.Display;
Jeff Brown89d55462012-09-19 11:33:42 -070031
32import java.io.BufferedInputStream;
33import java.io.BufferedOutputStream;
34import java.io.File;
35import java.io.FileNotFoundException;
36import java.io.FileOutputStream;
37import java.io.IOException;
38import java.io.InputStream;
Michael Wright1c9977b2016-07-12 13:30:10 -070039import java.io.PrintWriter;
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +010040import java.nio.charset.StandardCharsets;
Jeff Brown89d55462012-09-19 11:33:42 -070041import java.util.ArrayList;
Michael Wright1c9977b2016-07-12 13:30:10 -070042import java.util.HashMap;
43import java.util.Map;
Jeff Brown89d55462012-09-19 11:33:42 -070044
45import libcore.io.IoUtils;
46import libcore.util.Objects;
47
48/**
49 * Manages persistent state recorded by the display manager service as an XML file.
50 * Caller must acquire lock on the data store before accessing it.
51 *
52 * File format:
53 * <code>
54 * &lt;display-manager-state>
55 * &lt;remembered-wifi-displays>
56 * &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
Michael Wright1c9977b2016-07-12 13:30:10 -070057 * &lt;remembered-wifi-displays>
58 * &lt;display-states>
59 * &lt;display>
60 * &lt;color-mode>0&lt;/color-mode>
61 * &lt;/display>
62 * &lt;/display-states>
63 * &lt;/display-manager-state>
Jeff Brown89d55462012-09-19 11:33:42 -070064 * </code>
65 *
66 * TODO: refactor this to extract common code shared with the input manager's data store
67 */
68final class PersistentDataStore {
69 static final String TAG = "DisplayManager";
70
71 // Remembered Wifi display devices.
72 private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
73
Michael Wright1c9977b2016-07-12 13:30:10 -070074 // Display state by unique id.
75 private final HashMap<String, DisplayState> mDisplayStates =
76 new HashMap<String, DisplayState>();
77
Jeff Brown89d55462012-09-19 11:33:42 -070078 // The atomic file used to safely read or write the file.
79 private final AtomicFile mAtomicFile;
80
81 // True if the data has been loaded.
82 private boolean mLoaded;
83
84 // True if there are changes to be saved.
85 private boolean mDirty;
86
87 public PersistentDataStore() {
88 mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
89 }
90
91 public void saveIfNeeded() {
92 if (mDirty) {
93 save();
94 mDirty = false;
95 }
96 }
97
Jeff Brown74da1092012-11-07 16:02:13 -080098 public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
99 loadIfNeeded();
100 int index = findRememberedWifiDisplay(deviceAddress);
101 if (index >= 0) {
102 return mRememberedWifiDisplays.get(index);
103 }
104 return null;
105 }
106
Jeff Brown89d55462012-09-19 11:33:42 -0700107 public WifiDisplay[] getRememberedWifiDisplays() {
108 loadIfNeeded();
109 return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
110 }
111
112 public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
113 if (display != null) {
114 loadIfNeeded();
115
Jeff Brownc2b9ea62012-09-19 23:09:23 -0700116 String alias = null;
Jeff Brown89d55462012-09-19 11:33:42 -0700117 int index = findRememberedWifiDisplay(display.getDeviceAddress());
118 if (index >= 0) {
Jeff Brownc2b9ea62012-09-19 23:09:23 -0700119 alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
120 }
121 if (!Objects.equal(display.getDeviceAlias(), alias)) {
Chong Zhang21f60392013-06-04 18:01:46 -0700122 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
Chong Zhangab87a632013-06-11 10:25:49 -0700123 alias, display.isAvailable(), display.canConnect(), display.isRemembered());
Jeff Brown89d55462012-09-19 11:33:42 -0700124 }
125 }
126 return display;
127 }
128
129 public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
130 WifiDisplay[] results = displays;
131 if (results != null) {
132 int count = displays.length;
133 for (int i = 0; i < count; i++) {
134 WifiDisplay result = applyWifiDisplayAlias(displays[i]);
135 if (result != displays[i]) {
136 if (results == displays) {
137 results = new WifiDisplay[count];
138 System.arraycopy(displays, 0, results, 0, count);
139 }
140 results[i] = result;
141 }
142 }
143 }
144 return results;
145 }
146
147 public boolean rememberWifiDisplay(WifiDisplay display) {
148 loadIfNeeded();
149
150 int index = findRememberedWifiDisplay(display.getDeviceAddress());
151 if (index >= 0) {
152 WifiDisplay other = mRememberedWifiDisplays.get(index);
153 if (other.equals(display)) {
154 return false; // already remembered without change
155 }
156 mRememberedWifiDisplays.set(index, display);
157 } else {
158 mRememberedWifiDisplays.add(display);
159 }
160 setDirty();
161 return true;
162 }
163
Jeff Brown89d55462012-09-19 11:33:42 -0700164 public boolean forgetWifiDisplay(String deviceAddress) {
165 int index = findRememberedWifiDisplay(deviceAddress);
166 if (index >= 0) {
167 mRememberedWifiDisplays.remove(index);
168 setDirty();
169 return true;
170 }
171 return false;
172 }
173
174 private int findRememberedWifiDisplay(String deviceAddress) {
175 int count = mRememberedWifiDisplays.size();
176 for (int i = 0; i < count; i++) {
177 if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
178 return i;
179 }
180 }
181 return -1;
182 }
183
Michael Wright1c9977b2016-07-12 13:30:10 -0700184 public int getColorMode(DisplayDevice device) {
185 if (!device.hasStableUniqueId()) {
Damien Bargiacchi4364bbf2016-11-01 21:44:20 -0700186 return Display.COLOR_MODE_INVALID;
Michael Wright1c9977b2016-07-12 13:30:10 -0700187 }
188 DisplayState state = getDisplayState(device.getUniqueId(), false);
189 if (state == null) {
Damien Bargiacchi4364bbf2016-11-01 21:44:20 -0700190 return Display.COLOR_MODE_INVALID;
Michael Wright1c9977b2016-07-12 13:30:10 -0700191 }
192 return state.getColorMode();
193 }
194
195 public boolean setColorMode(DisplayDevice device, int colorMode) {
196 if (!device.hasStableUniqueId()) {
197 return false;
198 }
199 DisplayState state = getDisplayState(device.getUniqueId(), true);
200 if (state.setColorMode(colorMode)) {
201 setDirty();
202 return true;
203 }
204 return false;
205 }
206
207 private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
208 loadIfNeeded();
209 DisplayState state = mDisplayStates.get(uniqueId);
210 if (state == null && createIfAbsent) {
211 state = new DisplayState();
212 mDisplayStates.put(uniqueId, state);
213 setDirty();
214 }
215 return state;
216 }
217
218 public void loadIfNeeded() {
Jeff Brown89d55462012-09-19 11:33:42 -0700219 if (!mLoaded) {
220 load();
221 mLoaded = true;
222 }
223 }
224
225 private void setDirty() {
226 mDirty = true;
227 }
228
229 private void clearState() {
230 mRememberedWifiDisplays.clear();
231 }
232
233 private void load() {
234 clearState();
235
236 final InputStream is;
237 try {
238 is = mAtomicFile.openRead();
239 } catch (FileNotFoundException ex) {
240 return;
241 }
242
243 XmlPullParser parser;
244 try {
245 parser = Xml.newPullParser();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100246 parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name());
Jeff Brown89d55462012-09-19 11:33:42 -0700247 loadFromXml(parser);
248 } catch (IOException ex) {
249 Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
250 clearState();
251 } catch (XmlPullParserException ex) {
252 Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
253 clearState();
254 } finally {
255 IoUtils.closeQuietly(is);
256 }
257 }
258
259 private void save() {
260 final FileOutputStream os;
261 try {
262 os = mAtomicFile.startWrite();
263 boolean success = false;
264 try {
265 XmlSerializer serializer = new FastXmlSerializer();
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100266 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name());
Jeff Brown89d55462012-09-19 11:33:42 -0700267 saveToXml(serializer);
268 serializer.flush();
269 success = true;
270 } finally {
271 if (success) {
272 mAtomicFile.finishWrite(os);
273 } else {
274 mAtomicFile.failWrite(os);
275 }
276 }
277 } catch (IOException ex) {
278 Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
279 }
280 }
281
282 private void loadFromXml(XmlPullParser parser)
283 throws IOException, XmlPullParserException {
284 XmlUtils.beginDocument(parser, "display-manager-state");
285 final int outerDepth = parser.getDepth();
286 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
287 if (parser.getName().equals("remembered-wifi-displays")) {
288 loadRememberedWifiDisplaysFromXml(parser);
289 }
Michael Wright1c9977b2016-07-12 13:30:10 -0700290 if (parser.getName().equals("display-states")) {
291 loadDisplaysFromXml(parser);
292 }
Jeff Brown89d55462012-09-19 11:33:42 -0700293 }
294 }
295
296 private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
297 throws IOException, XmlPullParserException {
298 final int outerDepth = parser.getDepth();
299 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
300 if (parser.getName().equals("wifi-display")) {
301 String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
302 String deviceName = parser.getAttributeValue(null, "deviceName");
303 String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
304 if (deviceAddress == null || deviceName == null) {
305 throw new XmlPullParserException(
306 "Missing deviceAddress or deviceName attribute on wifi-display.");
307 }
308 if (findRememberedWifiDisplay(deviceAddress) >= 0) {
309 throw new XmlPullParserException(
310 "Found duplicate wifi display device address.");
311 }
312
313 mRememberedWifiDisplays.add(
Chong Zhangab87a632013-06-11 10:25:49 -0700314 new WifiDisplay(deviceAddress, deviceName, deviceAlias,
315 false, false, false));
Jeff Brown89d55462012-09-19 11:33:42 -0700316 }
317 }
318 }
319
Michael Wright1c9977b2016-07-12 13:30:10 -0700320 private void loadDisplaysFromXml(XmlPullParser parser)
321 throws IOException, XmlPullParserException {
322 final int outerDepth = parser.getDepth();
323 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
324 if (parser.getName().equals("display")) {
325 String uniqueId = parser.getAttributeValue(null, "unique-id");
326 if (uniqueId == null) {
327 throw new XmlPullParserException(
328 "Missing unique-id attribute on display.");
329 }
330 if (mDisplayStates.containsKey(uniqueId)) {
331 throw new XmlPullParserException("Found duplicate display.");
332 }
333
334 DisplayState state = new DisplayState();
335 state.loadFromXml(parser);
336 mDisplayStates.put(uniqueId, state);
337 }
338 }
339 }
340
Jeff Brown89d55462012-09-19 11:33:42 -0700341 private void saveToXml(XmlSerializer serializer) throws IOException {
342 serializer.startDocument(null, true);
343 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
344 serializer.startTag(null, "display-manager-state");
345 serializer.startTag(null, "remembered-wifi-displays");
346 for (WifiDisplay display : mRememberedWifiDisplays) {
347 serializer.startTag(null, "wifi-display");
348 serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
349 serializer.attribute(null, "deviceName", display.getDeviceName());
350 if (display.getDeviceAlias() != null) {
351 serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
352 }
353 serializer.endTag(null, "wifi-display");
354 }
355 serializer.endTag(null, "remembered-wifi-displays");
Michael Wright1c9977b2016-07-12 13:30:10 -0700356 serializer.startTag(null, "display-states");
357 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
358 final String uniqueId = entry.getKey();
359 final DisplayState state = entry.getValue();
360 serializer.startTag(null, "display");
361 serializer.attribute(null, "unique-id", uniqueId);
362 state.saveToXml(serializer);
363 serializer.endTag(null, "display");
364 }
365 serializer.endTag(null, "display-states");
Jeff Brown89d55462012-09-19 11:33:42 -0700366 serializer.endTag(null, "display-manager-state");
367 serializer.endDocument();
368 }
Michael Wright1c9977b2016-07-12 13:30:10 -0700369
370 public void dump(PrintWriter pw) {
371 pw.println("PersistentDataStore");
372 pw.println(" mLoaded=" + mLoaded);
373 pw.println(" mDirty=" + mDirty);
374 pw.println(" RememberedWifiDisplays:");
375 int i = 0;
376 for (WifiDisplay display : mRememberedWifiDisplays) {
377 pw.println(" " + i++ + ": " + display);
378 }
379 pw.println(" DisplayStates:");
380 i = 0;
381 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
382 pw.println(" " + i++ + ": " + entry.getKey());
383 entry.getValue().dump(pw, " ");
384 }
385 }
386
387 private static final class DisplayState {
388 private int mColorMode;
389
390 public boolean setColorMode(int colorMode) {
391 if (colorMode == mColorMode) {
392 return false;
393 }
394 mColorMode = colorMode;
395 return true;
396 }
397
398 public int getColorMode() {
399 return mColorMode;
400 }
401
402 public void loadFromXml(XmlPullParser parser)
403 throws IOException, XmlPullParserException {
404 final int outerDepth = parser.getDepth();
405
406 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
407 if (parser.getName().equals("color-mode")) {
408 String value = parser.nextText();
409 mColorMode = Integer.parseInt(value);
410 }
411 }
412 }
413
414 public void saveToXml(XmlSerializer serializer) throws IOException {
415 serializer.startTag(null, "color-mode");
416 serializer.text(Integer.toString(mColorMode));
417 serializer.endTag(null, "color-mode");
418 }
419
420 private void dump(final PrintWriter pw, final String prefix) {
421 pw.println(prefix + "ColorMode=" + mColorMode);
422 }
423 }
Wojciech Staszkiewicz9e9e2e72015-05-08 14:58:46 +0100424}