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; 30import android.view.Display; 31 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; 39import java.io.PrintWriter; 40import java.nio.charset.StandardCharsets; 41import java.util.ArrayList; 42import java.util.HashMap; 43import java.util.Map; 44 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 * <display-manager-state> 55 * <remembered-wifi-displays> 56 * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> 57 * <remembered-wifi-displays> 58 * <display-states> 59 * <display> 60 * <color-mode>0</color-mode> 61 * </display> 62 * </display-states> 63 * </display-manager-state> 64 * </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 74 // Display state by unique id. 75 private final HashMap<String, DisplayState> mDisplayStates = 76 new HashMap<String, DisplayState>(); 77 78 // 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 98 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 107 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 116 String alias = null; 117 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 118 if (index >= 0) { 119 alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); 120 } 121 if (!Objects.equal(display.getDeviceAlias(), alias)) { 122 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), 123 alias, display.isAvailable(), display.canConnect(), display.isRemembered()); 124 } 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 164 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 184 public int getColorMode(DisplayDevice device) { 185 if (!device.hasStableUniqueId()) { 186 return Display.COLOR_MODE_INVALID; 187 } 188 DisplayState state = getDisplayState(device.getUniqueId(), false); 189 if (state == null) { 190 return Display.COLOR_MODE_INVALID; 191 } 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() { 219 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(); 246 parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name()); 247 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(); 266 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name()); 267 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 } 290 if (parser.getName().equals("display-states")) { 291 loadDisplaysFromXml(parser); 292 } 293 } 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( 314 new WifiDisplay(deviceAddress, deviceName, deviceAlias, 315 false, false, false)); 316 } 317 } 318 } 319 320 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 341 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"); 356 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"); 366 serializer.endTag(null, "display-manager-state"); 367 serializer.endDocument(); 368 } 369 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 } 424} 425