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.graphics.Point; 27import android.hardware.display.WifiDisplay; 28import android.util.AtomicFile; 29import android.util.Slog; 30import android.util.Xml; 31import android.view.Display; 32 33import java.io.BufferedInputStream; 34import java.io.BufferedOutputStream; 35import java.io.File; 36import java.io.FileNotFoundException; 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.io.InputStream; 40import java.io.PrintWriter; 41import java.nio.charset.StandardCharsets; 42import java.util.ArrayList; 43import java.util.HashMap; 44import java.util.Map; 45 46import libcore.io.IoUtils; 47import libcore.util.Objects; 48 49/** 50 * Manages persistent state recorded by the display manager service as an XML file. 51 * Caller must acquire lock on the data store before accessing it. 52 * 53 * File format: 54 * <code> 55 * <display-manager-state> 56 * <remembered-wifi-displays> 57 * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> 58 * <remembered-wifi-displays> 59 * <display-states> 60 * <display> 61 * <color-mode>0</color-mode> 62 * </display> 63 * </display-states> 64 * <stable-device-values> 65 * <stable-display-height>1920<stable-display-height> 66 * <stable-display-width>1080<stable-display-width> 67 * </stable-device-values> 68 * </display-manager-state> 69 * </code> 70 * 71 * TODO: refactor this to extract common code shared with the input manager's data store 72 */ 73final class PersistentDataStore { 74 static final String TAG = "DisplayManager"; 75 76 // Remembered Wifi display devices. 77 private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); 78 79 // Display state by unique id. 80 private final HashMap<String, DisplayState> mDisplayStates = 81 new HashMap<String, DisplayState>(); 82 83 // Display values which should be stable across the device's lifetime. 84 private final StableDeviceValues mStableDeviceValues = new StableDeviceValues(); 85 86 // The atomic file used to safely read or write the file. 87 private final AtomicFile mAtomicFile; 88 89 // True if the data has been loaded. 90 private boolean mLoaded; 91 92 // True if there are changes to be saved. 93 private boolean mDirty; 94 95 public PersistentDataStore() { 96 mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml")); 97 } 98 99 public void saveIfNeeded() { 100 if (mDirty) { 101 save(); 102 mDirty = false; 103 } 104 } 105 106 public WifiDisplay getRememberedWifiDisplay(String deviceAddress) { 107 loadIfNeeded(); 108 int index = findRememberedWifiDisplay(deviceAddress); 109 if (index >= 0) { 110 return mRememberedWifiDisplays.get(index); 111 } 112 return null; 113 } 114 115 public WifiDisplay[] getRememberedWifiDisplays() { 116 loadIfNeeded(); 117 return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]); 118 } 119 120 public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) { 121 if (display != null) { 122 loadIfNeeded(); 123 124 String alias = null; 125 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 126 if (index >= 0) { 127 alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); 128 } 129 if (!Objects.equal(display.getDeviceAlias(), alias)) { 130 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), 131 alias, display.isAvailable(), display.canConnect(), display.isRemembered()); 132 } 133 } 134 return display; 135 } 136 137 public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { 138 WifiDisplay[] results = displays; 139 if (results != null) { 140 int count = displays.length; 141 for (int i = 0; i < count; i++) { 142 WifiDisplay result = applyWifiDisplayAlias(displays[i]); 143 if (result != displays[i]) { 144 if (results == displays) { 145 results = new WifiDisplay[count]; 146 System.arraycopy(displays, 0, results, 0, count); 147 } 148 results[i] = result; 149 } 150 } 151 } 152 return results; 153 } 154 155 public boolean rememberWifiDisplay(WifiDisplay display) { 156 loadIfNeeded(); 157 158 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 159 if (index >= 0) { 160 WifiDisplay other = mRememberedWifiDisplays.get(index); 161 if (other.equals(display)) { 162 return false; // already remembered without change 163 } 164 mRememberedWifiDisplays.set(index, display); 165 } else { 166 mRememberedWifiDisplays.add(display); 167 } 168 setDirty(); 169 return true; 170 } 171 172 public boolean forgetWifiDisplay(String deviceAddress) { 173 loadIfNeeded(); 174 int index = findRememberedWifiDisplay(deviceAddress); 175 if (index >= 0) { 176 mRememberedWifiDisplays.remove(index); 177 setDirty(); 178 return true; 179 } 180 return false; 181 } 182 183 private int findRememberedWifiDisplay(String deviceAddress) { 184 int count = mRememberedWifiDisplays.size(); 185 for (int i = 0; i < count; i++) { 186 if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) { 187 return i; 188 } 189 } 190 return -1; 191 } 192 193 public int getColorMode(DisplayDevice device) { 194 if (!device.hasStableUniqueId()) { 195 return Display.COLOR_MODE_INVALID; 196 } 197 DisplayState state = getDisplayState(device.getUniqueId(), false); 198 if (state == null) { 199 return Display.COLOR_MODE_INVALID; 200 } 201 return state.getColorMode(); 202 } 203 204 public boolean setColorMode(DisplayDevice device, int colorMode) { 205 if (!device.hasStableUniqueId()) { 206 return false; 207 } 208 DisplayState state = getDisplayState(device.getUniqueId(), true); 209 if (state.setColorMode(colorMode)) { 210 setDirty(); 211 return true; 212 } 213 return false; 214 } 215 216 public Point getStableDisplaySize() { 217 loadIfNeeded(); 218 return mStableDeviceValues.getDisplaySize(); 219 } 220 221 public void setStableDisplaySize(Point size) { 222 loadIfNeeded(); 223 if (mStableDeviceValues.setDisplaySize(size)) { 224 setDirty(); 225 } 226 } 227 228 private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) { 229 loadIfNeeded(); 230 DisplayState state = mDisplayStates.get(uniqueId); 231 if (state == null && createIfAbsent) { 232 state = new DisplayState(); 233 mDisplayStates.put(uniqueId, state); 234 setDirty(); 235 } 236 return state; 237 } 238 239 public void loadIfNeeded() { 240 if (!mLoaded) { 241 load(); 242 mLoaded = true; 243 } 244 } 245 246 private void setDirty() { 247 mDirty = true; 248 } 249 250 private void clearState() { 251 mRememberedWifiDisplays.clear(); 252 } 253 254 private void load() { 255 clearState(); 256 257 final InputStream is; 258 try { 259 is = mAtomicFile.openRead(); 260 } catch (FileNotFoundException ex) { 261 return; 262 } 263 264 XmlPullParser parser; 265 try { 266 parser = Xml.newPullParser(); 267 parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name()); 268 loadFromXml(parser); 269 } catch (IOException ex) { 270 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 271 clearState(); 272 } catch (XmlPullParserException ex) { 273 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 274 clearState(); 275 } finally { 276 IoUtils.closeQuietly(is); 277 } 278 } 279 280 private void save() { 281 final FileOutputStream os; 282 try { 283 os = mAtomicFile.startWrite(); 284 boolean success = false; 285 try { 286 XmlSerializer serializer = new FastXmlSerializer(); 287 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name()); 288 saveToXml(serializer); 289 serializer.flush(); 290 success = true; 291 } finally { 292 if (success) { 293 mAtomicFile.finishWrite(os); 294 } else { 295 mAtomicFile.failWrite(os); 296 } 297 } 298 } catch (IOException ex) { 299 Slog.w(TAG, "Failed to save display manager persistent store data.", ex); 300 } 301 } 302 303 private void loadFromXml(XmlPullParser parser) 304 throws IOException, XmlPullParserException { 305 XmlUtils.beginDocument(parser, "display-manager-state"); 306 final int outerDepth = parser.getDepth(); 307 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 308 if (parser.getName().equals("remembered-wifi-displays")) { 309 loadRememberedWifiDisplaysFromXml(parser); 310 } 311 if (parser.getName().equals("display-states")) { 312 loadDisplaysFromXml(parser); 313 } 314 if (parser.getName().equals("stable-device-values")) { 315 mStableDeviceValues.loadFromXml(parser); 316 } 317 } 318 } 319 320 private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser) 321 throws IOException, XmlPullParserException { 322 final int outerDepth = parser.getDepth(); 323 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 324 if (parser.getName().equals("wifi-display")) { 325 String deviceAddress = parser.getAttributeValue(null, "deviceAddress"); 326 String deviceName = parser.getAttributeValue(null, "deviceName"); 327 String deviceAlias = parser.getAttributeValue(null, "deviceAlias"); 328 if (deviceAddress == null || deviceName == null) { 329 throw new XmlPullParserException( 330 "Missing deviceAddress or deviceName attribute on wifi-display."); 331 } 332 if (findRememberedWifiDisplay(deviceAddress) >= 0) { 333 throw new XmlPullParserException( 334 "Found duplicate wifi display device address."); 335 } 336 337 mRememberedWifiDisplays.add( 338 new WifiDisplay(deviceAddress, deviceName, deviceAlias, 339 false, false, false)); 340 } 341 } 342 } 343 344 private void loadDisplaysFromXml(XmlPullParser parser) 345 throws IOException, XmlPullParserException { 346 final int outerDepth = parser.getDepth(); 347 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 348 if (parser.getName().equals("display")) { 349 String uniqueId = parser.getAttributeValue(null, "unique-id"); 350 if (uniqueId == null) { 351 throw new XmlPullParserException( 352 "Missing unique-id attribute on display."); 353 } 354 if (mDisplayStates.containsKey(uniqueId)) { 355 throw new XmlPullParserException("Found duplicate display."); 356 } 357 358 DisplayState state = new DisplayState(); 359 state.loadFromXml(parser); 360 mDisplayStates.put(uniqueId, state); 361 } 362 } 363 } 364 365 private void saveToXml(XmlSerializer serializer) throws IOException { 366 serializer.startDocument(null, true); 367 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 368 serializer.startTag(null, "display-manager-state"); 369 serializer.startTag(null, "remembered-wifi-displays"); 370 for (WifiDisplay display : mRememberedWifiDisplays) { 371 serializer.startTag(null, "wifi-display"); 372 serializer.attribute(null, "deviceAddress", display.getDeviceAddress()); 373 serializer.attribute(null, "deviceName", display.getDeviceName()); 374 if (display.getDeviceAlias() != null) { 375 serializer.attribute(null, "deviceAlias", display.getDeviceAlias()); 376 } 377 serializer.endTag(null, "wifi-display"); 378 } 379 serializer.endTag(null, "remembered-wifi-displays"); 380 serializer.startTag(null, "display-states"); 381 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) { 382 final String uniqueId = entry.getKey(); 383 final DisplayState state = entry.getValue(); 384 serializer.startTag(null, "display"); 385 serializer.attribute(null, "unique-id", uniqueId); 386 state.saveToXml(serializer); 387 serializer.endTag(null, "display"); 388 } 389 serializer.endTag(null, "display-states"); 390 serializer.startTag(null, "stable-device-values"); 391 mStableDeviceValues.saveToXml(serializer); 392 serializer.endTag(null, "stable-device-values"); 393 serializer.endTag(null, "display-manager-state"); 394 serializer.endDocument(); 395 } 396 397 public void dump(PrintWriter pw) { 398 pw.println("PersistentDataStore"); 399 pw.println(" mLoaded=" + mLoaded); 400 pw.println(" mDirty=" + mDirty); 401 pw.println(" RememberedWifiDisplays:"); 402 int i = 0; 403 for (WifiDisplay display : mRememberedWifiDisplays) { 404 pw.println(" " + i++ + ": " + display); 405 } 406 pw.println(" DisplayStates:"); 407 i = 0; 408 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) { 409 pw.println(" " + i++ + ": " + entry.getKey()); 410 entry.getValue().dump(pw, " "); 411 } 412 pw.println(" StableDeviceValues:"); 413 mStableDeviceValues.dump(pw, " "); 414 } 415 416 private static final class DisplayState { 417 private int mColorMode; 418 419 public boolean setColorMode(int colorMode) { 420 if (colorMode == mColorMode) { 421 return false; 422 } 423 mColorMode = colorMode; 424 return true; 425 } 426 427 public int getColorMode() { 428 return mColorMode; 429 } 430 431 public void loadFromXml(XmlPullParser parser) 432 throws IOException, XmlPullParserException { 433 final int outerDepth = parser.getDepth(); 434 435 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 436 if (parser.getName().equals("color-mode")) { 437 String value = parser.nextText(); 438 mColorMode = Integer.parseInt(value); 439 } 440 } 441 } 442 443 public void saveToXml(XmlSerializer serializer) throws IOException { 444 serializer.startTag(null, "color-mode"); 445 serializer.text(Integer.toString(mColorMode)); 446 serializer.endTag(null, "color-mode"); 447 } 448 449 public void dump(final PrintWriter pw, final String prefix) { 450 pw.println(prefix + "ColorMode=" + mColorMode); 451 } 452 } 453 454 private static final class StableDeviceValues { 455 private int mWidth; 456 private int mHeight; 457 458 private Point getDisplaySize() { 459 return new Point(mWidth, mHeight); 460 } 461 462 public boolean setDisplaySize(Point r) { 463 if (mWidth != r.x || mHeight != r.y) { 464 mWidth = r.x; 465 mHeight = r.y; 466 return true; 467 } 468 return false; 469 } 470 471 public void loadFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { 472 final int outerDepth = parser.getDepth(); 473 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 474 switch (parser.getName()) { 475 case "stable-display-width": 476 mWidth = loadIntValue(parser); 477 break; 478 case "stable-display-height": 479 mHeight = loadIntValue(parser); 480 break; 481 } 482 } 483 } 484 485 private static int loadIntValue(XmlPullParser parser) 486 throws IOException, XmlPullParserException { 487 try { 488 String value = parser.nextText(); 489 return Integer.parseInt(value); 490 } catch (NumberFormatException nfe) { 491 return 0; 492 } 493 } 494 495 public void saveToXml(XmlSerializer serializer) throws IOException { 496 if (mWidth > 0 && mHeight > 0) { 497 serializer.startTag(null, "stable-display-width"); 498 serializer.text(Integer.toString(mWidth)); 499 serializer.endTag(null, "stable-display-width"); 500 serializer.startTag(null, "stable-display-height"); 501 serializer.text(Integer.toString(mHeight)); 502 serializer.endTag(null, "stable-display-height"); 503 } 504 } 505 506 public void dump(final PrintWriter pw, final String prefix) { 507 pw.println(prefix + "StableDisplayWidth=" + mWidth); 508 pw.println(prefix + "StableDisplayHeight=" + mHeight); 509 } 510 } 511} 512