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 android.annotation.Nullable; 20 import android.graphics.Point; 21 import android.hardware.display.BrightnessConfiguration; 22 import android.hardware.display.WifiDisplay; 23 import android.os.Handler; 24 import android.util.AtomicFile; 25 import android.util.Slog; 26 import android.util.SparseArray; 27 import android.util.SparseLongArray; 28 import android.util.TimeUtils; 29 import android.util.TypedXmlPullParser; 30 import android.util.TypedXmlSerializer; 31 import android.util.Xml; 32 import android.view.Display; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.os.BackgroundThread; 36 import com.android.internal.util.XmlUtils; 37 38 import libcore.io.IoUtils; 39 40 import org.xmlpull.v1.XmlPullParserException; 41 42 import java.io.ByteArrayOutputStream; 43 import java.io.File; 44 import java.io.FileNotFoundException; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.OutputStream; 49 import java.io.PrintWriter; 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.Map; 53 import java.util.Objects; 54 55 /** 56 * Manages persistent state recorded by the display manager service as an XML file. 57 * Caller must acquire lock on the data store before accessing it. 58 * 59 * File format: 60 * <code> 61 * <display-manager-state> 62 * <remembered-wifi-displays> 63 * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> 64 * <remembered-wifi-displays> 65 * <display-states> 66 * <display unique-id="XXXXXXX"> 67 * <color-mode>0</color-mode> 68 * <brightness-value>0</brightness-value> 69 * <brightness-configurations> 70 * <brightness-configuration user-serial="0" package-name="com.example" 71 * timestamp="1234"> 72 * <brightness-curve description="some text"> 73 * <brightness-point lux="0" nits="13.25"/> 74 * <brightness-point lux="20" nits="35.94"/> 75 * </brightness-curve> 76 * </brightness-configuration> 77 * </brightness-configurations> 78 * <display-mode>0< 79 * <resolution-width>1080</resolution-width> 80 * <resolution-height>1920</resolution-height> 81 * <refresh-rate>60</refresh-rate> 82 * </display-mode> 83 * </display> 84 * </display-states> 85 * <stable-device-values> 86 * <stable-display-height>1920</stable-display-height> 87 * <stable-display-width>1080</stable-display-width> 88 * </stable-device-values> 89 * <brightness-configurations> 90 * <brightness-configuration user-serial="0" package-name="com.example" timestamp="1234"> 91 * <brightness-curve description="some text"> 92 * <brightness-point lux="0" nits="13.25"/> 93 * <brightness-point lux="20" nits="35.94"/> 94 * </brightness-curve> 95 * </brightness-configuration> 96 * </brightness-configurations> 97 * <brightness-nits-for-default-display>600</brightness-nits-for-default-display> 98 * </display-manager-state> 99 * </code> 100 * 101 * TODO: refactor this to extract common code shared with the input manager's data store 102 */ 103 final class PersistentDataStore { 104 static final String TAG = "DisplayManager.PersistentDataStore"; 105 106 private static final String TAG_DISPLAY_MANAGER_STATE = "display-manager-state"; 107 108 private static final String TAG_REMEMBERED_WIFI_DISPLAYS = "remembered-wifi-displays"; 109 private static final String TAG_WIFI_DISPLAY = "wifi-display"; 110 private static final String ATTR_DEVICE_ADDRESS = "deviceAddress"; 111 private static final String ATTR_DEVICE_NAME = "deviceName"; 112 private static final String ATTR_DEVICE_ALIAS = "deviceAlias"; 113 114 private static final String TAG_DISPLAY_STATES = "display-states"; 115 private static final String TAG_DISPLAY = "display"; 116 private static final String TAG_COLOR_MODE = "color-mode"; 117 private static final String TAG_BRIGHTNESS_VALUE = "brightness-value"; 118 private static final String ATTR_UNIQUE_ID = "unique-id"; 119 120 private static final String TAG_STABLE_DEVICE_VALUES = "stable-device-values"; 121 private static final String TAG_STABLE_DISPLAY_HEIGHT = "stable-display-height"; 122 private static final String TAG_STABLE_DISPLAY_WIDTH = "stable-display-width"; 123 124 private static final String TAG_BRIGHTNESS_CONFIGURATIONS = "brightness-configurations"; 125 private static final String TAG_BRIGHTNESS_CONFIGURATION = "brightness-configuration"; 126 private static final String ATTR_USER_SERIAL = "user-serial"; 127 private static final String ATTR_PACKAGE_NAME = "package-name"; 128 private static final String ATTR_TIME_STAMP = "timestamp"; 129 130 private static final String TAG_RESOLUTION_WIDTH = "resolution-width"; 131 private static final String TAG_RESOLUTION_HEIGHT = "resolution-height"; 132 private static final String TAG_REFRESH_RATE = "refresh-rate"; 133 134 private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY = 135 "brightness-nits-for-default-display"; 136 137 // Remembered Wifi display devices. 138 private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); 139 140 // Display state by unique id. 141 private final HashMap<String, DisplayState> mDisplayStates = 142 new HashMap<String, DisplayState>(); 143 144 private float mBrightnessNitsForDefaultDisplay = -1; 145 146 // Display values which should be stable across the device's lifetime. 147 private final StableDeviceValues mStableDeviceValues = new StableDeviceValues(); 148 149 // Brightness configuration by user 150 private BrightnessConfigurations mGlobalBrightnessConfigurations = 151 new BrightnessConfigurations(); 152 153 // True if the data has been loaded. 154 private boolean mLoaded; 155 156 // True if there are changes to be saved. 157 private boolean mDirty; 158 159 // The interface for methods which should be replaced by the test harness. 160 private Injector mInjector; 161 162 private final Handler mHandler; 163 private final Object mFileAccessLock = new Object(); 164 PersistentDataStore()165 public PersistentDataStore() { 166 this(new Injector()); 167 } 168 169 @VisibleForTesting PersistentDataStore(Injector injector)170 PersistentDataStore(Injector injector) { 171 this(injector, new Handler(BackgroundThread.getHandler().getLooper())); 172 } 173 174 @VisibleForTesting PersistentDataStore(Injector injector, Handler handler)175 PersistentDataStore(Injector injector, Handler handler) { 176 mInjector = injector; 177 mHandler = handler; 178 } 179 saveIfNeeded()180 public void saveIfNeeded() { 181 if (mDirty) { 182 save(); 183 mDirty = false; 184 } 185 } 186 getRememberedWifiDisplay(String deviceAddress)187 public WifiDisplay getRememberedWifiDisplay(String deviceAddress) { 188 loadIfNeeded(); 189 int index = findRememberedWifiDisplay(deviceAddress); 190 if (index >= 0) { 191 return mRememberedWifiDisplays.get(index); 192 } 193 return null; 194 } 195 getRememberedWifiDisplays()196 public WifiDisplay[] getRememberedWifiDisplays() { 197 loadIfNeeded(); 198 return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]); 199 } 200 applyWifiDisplayAlias(WifiDisplay display)201 public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) { 202 if (display != null) { 203 loadIfNeeded(); 204 205 String alias = null; 206 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 207 if (index >= 0) { 208 alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); 209 } 210 if (!Objects.equals(display.getDeviceAlias(), alias)) { 211 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), 212 alias, display.isAvailable(), display.canConnect(), display.isRemembered()); 213 } 214 } 215 return display; 216 } 217 applyWifiDisplayAliases(WifiDisplay[] displays)218 public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { 219 WifiDisplay[] results = displays; 220 if (results != null) { 221 int count = displays.length; 222 for (int i = 0; i < count; i++) { 223 WifiDisplay result = applyWifiDisplayAlias(displays[i]); 224 if (result != displays[i]) { 225 if (results == displays) { 226 results = new WifiDisplay[count]; 227 System.arraycopy(displays, 0, results, 0, count); 228 } 229 results[i] = result; 230 } 231 } 232 } 233 return results; 234 } 235 rememberWifiDisplay(WifiDisplay display)236 public boolean rememberWifiDisplay(WifiDisplay display) { 237 loadIfNeeded(); 238 239 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 240 if (index >= 0) { 241 WifiDisplay other = mRememberedWifiDisplays.get(index); 242 if (other.equals(display)) { 243 return false; // already remembered without change 244 } 245 mRememberedWifiDisplays.set(index, display); 246 } else { 247 mRememberedWifiDisplays.add(display); 248 } 249 setDirty(); 250 return true; 251 } 252 forgetWifiDisplay(String deviceAddress)253 public boolean forgetWifiDisplay(String deviceAddress) { 254 loadIfNeeded(); 255 int index = findRememberedWifiDisplay(deviceAddress); 256 if (index >= 0) { 257 mRememberedWifiDisplays.remove(index); 258 setDirty(); 259 return true; 260 } 261 return false; 262 } 263 findRememberedWifiDisplay(String deviceAddress)264 private int findRememberedWifiDisplay(String deviceAddress) { 265 int count = mRememberedWifiDisplays.size(); 266 for (int i = 0; i < count; i++) { 267 if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) { 268 return i; 269 } 270 } 271 return -1; 272 } 273 getColorMode(DisplayDevice device)274 public int getColorMode(DisplayDevice device) { 275 if (!device.hasStableUniqueId()) { 276 return Display.COLOR_MODE_INVALID; 277 } 278 DisplayState state = getDisplayState(device.getUniqueId(), false); 279 if (state == null) { 280 return Display.COLOR_MODE_INVALID; 281 } 282 return state.getColorMode(); 283 } 284 setColorMode(DisplayDevice device, int colorMode)285 public boolean setColorMode(DisplayDevice device, int colorMode) { 286 if (!device.hasStableUniqueId()) { 287 return false; 288 } 289 DisplayState state = getDisplayState(device.getUniqueId(), true); 290 if (state.setColorMode(colorMode)) { 291 setDirty(); 292 return true; 293 } 294 return false; 295 } 296 getBrightness(DisplayDevice device)297 public float getBrightness(DisplayDevice device) { 298 if (device == null || !device.hasStableUniqueId()) { 299 return Float.NaN; 300 } 301 final DisplayState state = getDisplayState(device.getUniqueId(), false); 302 if (state == null) { 303 return Float.NaN; 304 } 305 return state.getBrightness(); 306 } 307 setBrightness(DisplayDevice displayDevice, float brightness)308 public boolean setBrightness(DisplayDevice displayDevice, float brightness) { 309 final String displayDeviceUniqueId = displayDevice.getUniqueId(); 310 if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { 311 return false; 312 } 313 final DisplayState state = getDisplayState(displayDeviceUniqueId, true); 314 if (state.setBrightness(brightness)) { 315 setDirty(); 316 return true; 317 } 318 return false; 319 } 320 getBrightnessNitsForDefaultDisplay()321 public float getBrightnessNitsForDefaultDisplay() { 322 return mBrightnessNitsForDefaultDisplay; 323 } 324 setBrightnessNitsForDefaultDisplay(float nits)325 public boolean setBrightnessNitsForDefaultDisplay(float nits) { 326 if (nits != mBrightnessNitsForDefaultDisplay) { 327 mBrightnessNitsForDefaultDisplay = nits; 328 setDirty(); 329 return true; 330 } 331 return false; 332 } 333 setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate)334 public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) { 335 final String displayDeviceUniqueId = displayDevice.getUniqueId(); 336 if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { 337 return false; 338 } 339 DisplayState state = getDisplayState(displayDevice.getUniqueId(), true); 340 if (state.setRefreshRate(refreshRate)) { 341 setDirty(); 342 return true; 343 } 344 return false; 345 } 346 getUserPreferredRefreshRate(DisplayDevice device)347 public float getUserPreferredRefreshRate(DisplayDevice device) { 348 if (device == null || !device.hasStableUniqueId()) { 349 return Float.NaN; 350 } 351 final DisplayState state = getDisplayState(device.getUniqueId(), false); 352 if (state == null) { 353 return Float.NaN; 354 } 355 return state.getRefreshRate(); 356 } 357 setUserPreferredResolution(DisplayDevice displayDevice, int width, int height)358 public boolean setUserPreferredResolution(DisplayDevice displayDevice, int width, int height) { 359 final String displayDeviceUniqueId = displayDevice.getUniqueId(); 360 if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { 361 return false; 362 } 363 DisplayState state = getDisplayState(displayDevice.getUniqueId(), true); 364 if (state.setResolution(width, height)) { 365 setDirty(); 366 return true; 367 } 368 return false; 369 } 370 getUserPreferredResolution(DisplayDevice displayDevice)371 public Point getUserPreferredResolution(DisplayDevice displayDevice) { 372 if (displayDevice == null || !displayDevice.hasStableUniqueId()) { 373 return null; 374 } 375 final DisplayState state = getDisplayState(displayDevice.getUniqueId(), false); 376 if (state == null) { 377 return null; 378 } 379 return state.getResolution(); 380 } 381 getStableDisplaySize()382 public Point getStableDisplaySize() { 383 loadIfNeeded(); 384 return mStableDeviceValues.getDisplaySize(); 385 } 386 setStableDisplaySize(Point size)387 public void setStableDisplaySize(Point size) { 388 loadIfNeeded(); 389 if (mStableDeviceValues.setDisplaySize(size)) { 390 setDirty(); 391 } 392 } 393 394 // Used for testing & reset setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, @Nullable String packageName)395 public void setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, 396 @Nullable String packageName) { 397 loadIfNeeded(); 398 if (mGlobalBrightnessConfigurations.setBrightnessConfigurationForUser(c, userSerial, 399 packageName)) { 400 401 setDirty(); 402 } 403 } 404 setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration, DisplayDevice device, int userSerial, String packageName)405 public boolean setBrightnessConfigurationForDisplayLocked(BrightnessConfiguration configuration, 406 DisplayDevice device, int userSerial, String packageName) { 407 if (device == null || !device.hasStableUniqueId()) { 408 return false; 409 } 410 DisplayState state = getDisplayState(device.getUniqueId(), /*createIfAbsent*/ true); 411 if (state.setBrightnessConfiguration(configuration, userSerial, packageName)) { 412 setDirty(); 413 return true; 414 } 415 return false; 416 } 417 418 getBrightnessConfigurationForDisplayLocked( String uniqueDisplayId, int userSerial)419 public BrightnessConfiguration getBrightnessConfigurationForDisplayLocked( 420 String uniqueDisplayId, int userSerial) { 421 loadIfNeeded(); 422 DisplayState state = mDisplayStates.get(uniqueDisplayId); 423 if (state != null) { 424 return state.getBrightnessConfiguration(userSerial); 425 } 426 return null; 427 } 428 getBrightnessConfiguration(int userSerial)429 public BrightnessConfiguration getBrightnessConfiguration(int userSerial) { 430 loadIfNeeded(); 431 return mGlobalBrightnessConfigurations.getBrightnessConfiguration(userSerial); 432 } 433 getDisplayState(String uniqueId, boolean createIfAbsent)434 private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) { 435 loadIfNeeded(); 436 DisplayState state = mDisplayStates.get(uniqueId); 437 if (state == null && createIfAbsent) { 438 state = new DisplayState(); 439 mDisplayStates.put(uniqueId, state); 440 setDirty(); 441 } 442 return state; 443 } 444 loadIfNeeded()445 public void loadIfNeeded() { 446 if (!mLoaded) { 447 load(); 448 mLoaded = true; 449 } 450 } 451 setDirty()452 private void setDirty() { 453 mDirty = true; 454 } 455 clearState()456 private void clearState() { 457 mRememberedWifiDisplays.clear(); 458 } 459 load()460 private void load() { 461 synchronized (mFileAccessLock) { 462 clearState(); 463 464 final InputStream is; 465 try { 466 is = mInjector.openRead(); 467 } catch (FileNotFoundException ex) { 468 return; 469 } 470 471 TypedXmlPullParser parser; 472 try { 473 parser = Xml.resolvePullParser(is); 474 loadFromXml(parser); 475 } catch (IOException ex) { 476 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 477 clearState(); 478 } catch (XmlPullParserException ex) { 479 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 480 clearState(); 481 } finally { 482 IoUtils.closeQuietly(is); 483 } 484 } 485 } 486 save()487 private void save() { 488 final ByteArrayOutputStream os; 489 try { 490 os = new ByteArrayOutputStream(); 491 492 TypedXmlSerializer serializer = Xml.resolveSerializer(os); 493 saveToXml(serializer); 494 serializer.flush(); 495 496 mHandler.removeCallbacksAndMessages(/* token */ null); 497 mHandler.post(() -> { 498 synchronized (mFileAccessLock) { 499 OutputStream fileOutput = null; 500 try { 501 fileOutput = mInjector.startWrite(); 502 os.writeTo(fileOutput); 503 fileOutput.flush(); 504 } catch (IOException ex) { 505 Slog.w(TAG, "Failed to save display manager persistent store data.", ex); 506 } finally { 507 if (fileOutput != null) { 508 mInjector.finishWrite(fileOutput, true); 509 } 510 } 511 } 512 }); 513 } catch (IOException ex) { 514 Slog.w(TAG, "Failed to process the XML serializer.", ex); 515 } 516 } 517 loadFromXml(TypedXmlPullParser parser)518 private void loadFromXml(TypedXmlPullParser parser) 519 throws IOException, XmlPullParserException { 520 XmlUtils.beginDocument(parser, TAG_DISPLAY_MANAGER_STATE); 521 final int outerDepth = parser.getDepth(); 522 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 523 if (parser.getName().equals(TAG_REMEMBERED_WIFI_DISPLAYS)) { 524 loadRememberedWifiDisplaysFromXml(parser); 525 } 526 if (parser.getName().equals(TAG_DISPLAY_STATES)) { 527 loadDisplaysFromXml(parser); 528 } 529 if (parser.getName().equals(TAG_STABLE_DEVICE_VALUES)) { 530 mStableDeviceValues.loadFromXml(parser); 531 } 532 if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) { 533 mGlobalBrightnessConfigurations.loadFromXml(parser); 534 } 535 if (parser.getName().equals(TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY)) { 536 String value = parser.nextText(); 537 mBrightnessNitsForDefaultDisplay = Float.parseFloat(value); 538 } 539 } 540 } 541 loadRememberedWifiDisplaysFromXml(TypedXmlPullParser parser)542 private void loadRememberedWifiDisplaysFromXml(TypedXmlPullParser parser) 543 throws IOException, XmlPullParserException { 544 final int outerDepth = parser.getDepth(); 545 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 546 if (parser.getName().equals(TAG_WIFI_DISPLAY)) { 547 String deviceAddress = parser.getAttributeValue(null, ATTR_DEVICE_ADDRESS); 548 String deviceName = parser.getAttributeValue(null, ATTR_DEVICE_NAME); 549 String deviceAlias = parser.getAttributeValue(null, ATTR_DEVICE_ALIAS); 550 if (deviceAddress == null || deviceName == null) { 551 throw new XmlPullParserException( 552 "Missing deviceAddress or deviceName attribute on wifi-display."); 553 } 554 if (findRememberedWifiDisplay(deviceAddress) >= 0) { 555 throw new XmlPullParserException( 556 "Found duplicate wifi display device address."); 557 } 558 559 mRememberedWifiDisplays.add( 560 new WifiDisplay(deviceAddress, deviceName, deviceAlias, 561 false, false, false)); 562 } 563 } 564 } 565 loadDisplaysFromXml(TypedXmlPullParser parser)566 private void loadDisplaysFromXml(TypedXmlPullParser parser) 567 throws IOException, XmlPullParserException { 568 final int outerDepth = parser.getDepth(); 569 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 570 if (parser.getName().equals(TAG_DISPLAY)) { 571 String uniqueId = parser.getAttributeValue(null, ATTR_UNIQUE_ID); 572 if (uniqueId == null) { 573 throw new XmlPullParserException( 574 "Missing unique-id attribute on display."); 575 } 576 if (mDisplayStates.containsKey(uniqueId)) { 577 throw new XmlPullParserException("Found duplicate display."); 578 } 579 580 DisplayState state = new DisplayState(); 581 state.loadFromXml(parser); 582 mDisplayStates.put(uniqueId, state); 583 } 584 } 585 } 586 saveToXml(TypedXmlSerializer serializer)587 private void saveToXml(TypedXmlSerializer serializer) throws IOException { 588 serializer.startDocument(null, true); 589 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 590 serializer.startTag(null, TAG_DISPLAY_MANAGER_STATE); 591 serializer.startTag(null, TAG_REMEMBERED_WIFI_DISPLAYS); 592 for (WifiDisplay display : mRememberedWifiDisplays) { 593 serializer.startTag(null, TAG_WIFI_DISPLAY); 594 serializer.attribute(null, ATTR_DEVICE_ADDRESS, display.getDeviceAddress()); 595 serializer.attribute(null, ATTR_DEVICE_NAME, display.getDeviceName()); 596 if (display.getDeviceAlias() != null) { 597 serializer.attribute(null, ATTR_DEVICE_ALIAS, display.getDeviceAlias()); 598 } 599 serializer.endTag(null, TAG_WIFI_DISPLAY); 600 } 601 serializer.endTag(null, TAG_REMEMBERED_WIFI_DISPLAYS); 602 serializer.startTag(null, TAG_DISPLAY_STATES); 603 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) { 604 final String uniqueId = entry.getKey(); 605 final DisplayState state = entry.getValue(); 606 serializer.startTag(null, TAG_DISPLAY); 607 serializer.attribute(null, ATTR_UNIQUE_ID, uniqueId); 608 state.saveToXml(serializer); 609 serializer.endTag(null, TAG_DISPLAY); 610 } 611 serializer.endTag(null, TAG_DISPLAY_STATES); 612 serializer.startTag(null, TAG_STABLE_DEVICE_VALUES); 613 mStableDeviceValues.saveToXml(serializer); 614 serializer.endTag(null, TAG_STABLE_DEVICE_VALUES); 615 serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); 616 mGlobalBrightnessConfigurations.saveToXml(serializer); 617 serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); 618 serializer.startTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY); 619 serializer.text(Float.toString(mBrightnessNitsForDefaultDisplay)); 620 serializer.endTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY); 621 serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE); 622 serializer.endDocument(); 623 } 624 dump(PrintWriter pw)625 public void dump(PrintWriter pw) { 626 pw.println("PersistentDataStore"); 627 pw.println(" mLoaded=" + mLoaded); 628 pw.println(" mDirty=" + mDirty); 629 pw.println(" RememberedWifiDisplays:"); 630 int i = 0; 631 for (WifiDisplay display : mRememberedWifiDisplays) { 632 pw.println(" " + i++ + ": " + display); 633 } 634 pw.println(" DisplayStates:"); 635 i = 0; 636 for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) { 637 pw.println(" " + i++ + ": " + entry.getKey()); 638 entry.getValue().dump(pw, " "); 639 } 640 pw.println(" StableDeviceValues:"); 641 mStableDeviceValues.dump(pw, " "); 642 pw.println(" GlobalBrightnessConfigurations:"); 643 mGlobalBrightnessConfigurations.dump(pw, " "); 644 pw.println(" mBrightnessNitsForDefaultDisplay=" + mBrightnessNitsForDefaultDisplay); 645 } 646 647 private static final class DisplayState { 648 private int mColorMode; 649 private float mBrightness = Float.NaN; 650 private int mWidth; 651 private int mHeight; 652 private float mRefreshRate; 653 654 // Brightness configuration by user 655 private BrightnessConfigurations mDisplayBrightnessConfigurations = 656 new BrightnessConfigurations(); 657 setColorMode(int colorMode)658 public boolean setColorMode(int colorMode) { 659 if (colorMode == mColorMode) { 660 return false; 661 } 662 mColorMode = colorMode; 663 return true; 664 } 665 getColorMode()666 public int getColorMode() { 667 return mColorMode; 668 } 669 setBrightness(float brightness)670 public boolean setBrightness(float brightness) { 671 if (brightness == mBrightness) { 672 return false; 673 } 674 mBrightness = brightness; 675 return true; 676 } 677 getBrightness()678 public float getBrightness() { 679 return mBrightness; 680 } 681 setBrightnessConfiguration(BrightnessConfiguration configuration, int userSerial, String packageName)682 public boolean setBrightnessConfiguration(BrightnessConfiguration configuration, 683 int userSerial, String packageName) { 684 mDisplayBrightnessConfigurations.setBrightnessConfigurationForUser( 685 configuration, userSerial, packageName); 686 return true; 687 } 688 getBrightnessConfiguration(int userSerial)689 public BrightnessConfiguration getBrightnessConfiguration(int userSerial) { 690 return mDisplayBrightnessConfigurations.mConfigurations.get(userSerial); 691 } 692 setResolution(int width, int height)693 public boolean setResolution(int width, int height) { 694 if (width == mWidth && height == mHeight) { 695 return false; 696 } 697 mWidth = width; 698 mHeight = height; 699 return true; 700 } 701 getResolution()702 public Point getResolution() { 703 return new Point(mWidth, mHeight); 704 } 705 setRefreshRate(float refreshRate)706 public boolean setRefreshRate(float refreshRate) { 707 if (refreshRate == mRefreshRate) { 708 return false; 709 } 710 mRefreshRate = refreshRate; 711 return true; 712 } 713 getRefreshRate()714 public float getRefreshRate() { 715 return mRefreshRate; 716 } 717 loadFromXml(TypedXmlPullParser parser)718 public void loadFromXml(TypedXmlPullParser parser) 719 throws IOException, XmlPullParserException { 720 final int outerDepth = parser.getDepth(); 721 722 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 723 switch (parser.getName()) { 724 case TAG_COLOR_MODE: 725 String value = parser.nextText(); 726 mColorMode = Integer.parseInt(value); 727 break; 728 case TAG_BRIGHTNESS_VALUE: 729 String brightness = parser.nextText(); 730 try { 731 mBrightness = Float.parseFloat(brightness); 732 } catch (NumberFormatException e) { 733 mBrightness = Float.NaN; 734 } 735 break; 736 case TAG_BRIGHTNESS_CONFIGURATIONS: 737 mDisplayBrightnessConfigurations.loadFromXml(parser); 738 break; 739 case TAG_RESOLUTION_WIDTH: 740 String width = parser.nextText(); 741 mWidth = Integer.parseInt(width); 742 break; 743 case TAG_RESOLUTION_HEIGHT: 744 String height = parser.nextText(); 745 mHeight = Integer.parseInt(height); 746 break; 747 case TAG_REFRESH_RATE: 748 String refreshRate = parser.nextText(); 749 mRefreshRate = Float.parseFloat(refreshRate); 750 break; 751 } 752 } 753 } 754 saveToXml(TypedXmlSerializer serializer)755 public void saveToXml(TypedXmlSerializer serializer) throws IOException { 756 serializer.startTag(null, TAG_COLOR_MODE); 757 serializer.text(Integer.toString(mColorMode)); 758 serializer.endTag(null, TAG_COLOR_MODE); 759 760 serializer.startTag(null, TAG_BRIGHTNESS_VALUE); 761 if (!Float.isNaN(mBrightness)) { 762 serializer.text(Float.toString(mBrightness)); 763 } 764 serializer.endTag(null, TAG_BRIGHTNESS_VALUE); 765 766 serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); 767 mDisplayBrightnessConfigurations.saveToXml(serializer); 768 serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS); 769 770 serializer.startTag(null, TAG_RESOLUTION_WIDTH); 771 serializer.text(Integer.toString(mWidth)); 772 serializer.endTag(null, TAG_RESOLUTION_WIDTH); 773 774 serializer.startTag(null, TAG_RESOLUTION_HEIGHT); 775 serializer.text(Integer.toString(mHeight)); 776 serializer.endTag(null, TAG_RESOLUTION_HEIGHT); 777 778 serializer.startTag(null, TAG_REFRESH_RATE); 779 serializer.text(Float.toString(mRefreshRate)); 780 serializer.endTag(null, TAG_REFRESH_RATE); 781 } 782 dump(final PrintWriter pw, final String prefix)783 public void dump(final PrintWriter pw, final String prefix) { 784 pw.println(prefix + "ColorMode=" + mColorMode); 785 pw.println(prefix + "BrightnessValue=" + mBrightness); 786 pw.println(prefix + "DisplayBrightnessConfigurations: "); 787 mDisplayBrightnessConfigurations.dump(pw, prefix); 788 pw.println(prefix + "Resolution=" + mWidth + " " + mHeight); 789 pw.println(prefix + "RefreshRate=" + mRefreshRate); 790 } 791 } 792 793 private static final class StableDeviceValues { 794 private int mWidth; 795 private int mHeight; 796 getDisplaySize()797 private Point getDisplaySize() { 798 return new Point(mWidth, mHeight); 799 } 800 setDisplaySize(Point r)801 public boolean setDisplaySize(Point r) { 802 if (mWidth != r.x || mHeight != r.y) { 803 mWidth = r.x; 804 mHeight = r.y; 805 return true; 806 } 807 return false; 808 } 809 loadFromXml(TypedXmlPullParser parser)810 public void loadFromXml(TypedXmlPullParser parser) 811 throws IOException, XmlPullParserException { 812 final int outerDepth = parser.getDepth(); 813 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 814 switch (parser.getName()) { 815 case TAG_STABLE_DISPLAY_WIDTH: 816 mWidth = loadIntValue(parser); 817 break; 818 case TAG_STABLE_DISPLAY_HEIGHT: 819 mHeight = loadIntValue(parser); 820 break; 821 } 822 } 823 } 824 loadIntValue(TypedXmlPullParser parser)825 private static int loadIntValue(TypedXmlPullParser parser) 826 throws IOException, XmlPullParserException { 827 try { 828 String value = parser.nextText(); 829 return Integer.parseInt(value); 830 } catch (NumberFormatException nfe) { 831 return 0; 832 } 833 } 834 saveToXml(TypedXmlSerializer serializer)835 public void saveToXml(TypedXmlSerializer serializer) throws IOException { 836 if (mWidth > 0 && mHeight > 0) { 837 serializer.startTag(null, TAG_STABLE_DISPLAY_WIDTH); 838 serializer.text(Integer.toString(mWidth)); 839 serializer.endTag(null, TAG_STABLE_DISPLAY_WIDTH); 840 serializer.startTag(null, TAG_STABLE_DISPLAY_HEIGHT); 841 serializer.text(Integer.toString(mHeight)); 842 serializer.endTag(null, TAG_STABLE_DISPLAY_HEIGHT); 843 } 844 } 845 dump(final PrintWriter pw, final String prefix)846 public void dump(final PrintWriter pw, final String prefix) { 847 pw.println(prefix + "StableDisplayWidth=" + mWidth); 848 pw.println(prefix + "StableDisplayHeight=" + mHeight); 849 } 850 } 851 852 private static final class BrightnessConfigurations { 853 // Maps from a user ID to the users' given brightness configuration 854 private final SparseArray<BrightnessConfiguration> mConfigurations; 855 // Timestamp of time the configuration was set. 856 private final SparseLongArray mTimeStamps; 857 // Package that set the configuration. 858 private final SparseArray<String> mPackageNames; 859 BrightnessConfigurations()860 public BrightnessConfigurations() { 861 mConfigurations = new SparseArray<>(); 862 mTimeStamps = new SparseLongArray(); 863 mPackageNames = new SparseArray<>(); 864 } 865 setBrightnessConfigurationForUser(BrightnessConfiguration c, int userSerial, String packageName)866 private boolean setBrightnessConfigurationForUser(BrightnessConfiguration c, 867 int userSerial, String packageName) { 868 BrightnessConfiguration currentConfig = mConfigurations.get(userSerial); 869 if (currentConfig != c && (currentConfig == null || !currentConfig.equals(c))) { 870 if (c != null) { 871 if (packageName == null) { 872 mPackageNames.remove(userSerial); 873 } else { 874 mPackageNames.put(userSerial, packageName); 875 } 876 mTimeStamps.put(userSerial, System.currentTimeMillis()); 877 mConfigurations.put(userSerial, c); 878 } else { 879 mPackageNames.remove(userSerial); 880 mTimeStamps.delete(userSerial); 881 mConfigurations.remove(userSerial); 882 } 883 return true; 884 } 885 return false; 886 } 887 getBrightnessConfiguration(int userSerial)888 public BrightnessConfiguration getBrightnessConfiguration(int userSerial) { 889 return mConfigurations.get(userSerial); 890 } 891 loadFromXml(TypedXmlPullParser parser)892 public void loadFromXml(TypedXmlPullParser parser) 893 throws IOException, XmlPullParserException { 894 final int outerDepth = parser.getDepth(); 895 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 896 if (TAG_BRIGHTNESS_CONFIGURATION.equals(parser.getName())) { 897 int userSerial; 898 try { 899 userSerial = parser.getAttributeInt(null, ATTR_USER_SERIAL); 900 } catch (NumberFormatException nfe) { 901 userSerial = -1; 902 Slog.e(TAG, "Failed to read in brightness configuration", nfe); 903 } 904 905 String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 906 long timeStamp = parser.getAttributeLong(null, ATTR_TIME_STAMP, -1); 907 908 try { 909 BrightnessConfiguration config = 910 BrightnessConfiguration.loadFromXml(parser); 911 if (userSerial >= 0 && config != null) { 912 mConfigurations.put(userSerial, config); 913 if (timeStamp != -1) { 914 mTimeStamps.put(userSerial, timeStamp); 915 } 916 if (packageName != null) { 917 mPackageNames.put(userSerial, packageName); 918 } 919 } 920 } catch (IllegalArgumentException iae) { 921 Slog.e(TAG, "Failed to load brightness configuration!", iae); 922 } 923 } 924 } 925 } 926 saveToXml(TypedXmlSerializer serializer)927 public void saveToXml(TypedXmlSerializer serializer) throws IOException { 928 for (int i = 0; i < mConfigurations.size(); i++) { 929 final int userSerial = mConfigurations.keyAt(i); 930 final BrightnessConfiguration config = mConfigurations.valueAt(i); 931 932 serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATION); 933 serializer.attributeInt(null, ATTR_USER_SERIAL, userSerial); 934 String packageName = mPackageNames.get(userSerial); 935 if (packageName != null) { 936 serializer.attribute(null, ATTR_PACKAGE_NAME, packageName); 937 } 938 long timestamp = mTimeStamps.get(userSerial, -1); 939 if (timestamp != -1) { 940 serializer.attributeLong(null, ATTR_TIME_STAMP, timestamp); 941 } 942 config.saveToXml(serializer); 943 serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATION); 944 } 945 } 946 dump(final PrintWriter pw, final String prefix)947 public void dump(final PrintWriter pw, final String prefix) { 948 for (int i = 0; i < mConfigurations.size(); i++) { 949 final int userSerial = mConfigurations.keyAt(i); 950 long time = mTimeStamps.get(userSerial, -1); 951 String packageName = mPackageNames.get(userSerial); 952 pw.println(prefix + "User " + userSerial + ":"); 953 if (time != -1) { 954 pw.println(prefix + " set at: " + TimeUtils.formatForLogging(time)); 955 } 956 if (packageName != null) { 957 pw.println(prefix + " set by: " + packageName); 958 } 959 pw.println(prefix + " " + mConfigurations.valueAt(i)); 960 } 961 } 962 } 963 964 @VisibleForTesting 965 static class Injector { 966 private final AtomicFile mAtomicFile; 967 Injector()968 public Injector() { 969 mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"), 970 "display-state"); 971 } 972 openRead()973 public InputStream openRead() throws FileNotFoundException { 974 return mAtomicFile.openRead(); 975 } 976 startWrite()977 public OutputStream startWrite() throws IOException { 978 return mAtomicFile.startWrite(); 979 } 980 finishWrite(OutputStream os, boolean success)981 public void finishWrite(OutputStream os, boolean success) { 982 if (!(os instanceof FileOutputStream)) { 983 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os); 984 } 985 FileOutputStream fos = (FileOutputStream) os; 986 if (success) { 987 mAtomicFile.finishWrite(fos); 988 } else { 989 mAtomicFile.failWrite(fos); 990 } 991 } 992 } 993 } 994