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