1 /* 2 * Copyright (C) 2013 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.wm; 18 19 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY; 20 import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY; 21 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; 22 23 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO; 24 import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED; 25 import static com.android.server.wm.DisplayRotation.FIXED_TO_USER_ROTATION_DEFAULT; 26 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 27 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 28 29 import android.annotation.IntDef; 30 import android.annotation.Nullable; 31 import android.app.WindowConfiguration; 32 import android.os.Environment; 33 import android.os.FileUtils; 34 import android.provider.Settings; 35 import android.util.AtomicFile; 36 import android.util.Slog; 37 import android.util.Xml; 38 import android.view.Display; 39 import android.view.DisplayAddress; 40 import android.view.DisplayInfo; 41 import android.view.Surface; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.util.FastXmlSerializer; 45 import com.android.internal.util.XmlUtils; 46 import com.android.server.policy.WindowManagerPolicy; 47 import com.android.server.wm.DisplayContent.ForceScalingMode; 48 49 import org.xmlpull.v1.XmlPullParser; 50 import org.xmlpull.v1.XmlPullParserException; 51 import org.xmlpull.v1.XmlSerializer; 52 53 import java.io.File; 54 import java.io.FileNotFoundException; 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.io.InputStream; 58 import java.io.OutputStream; 59 import java.nio.charset.StandardCharsets; 60 import java.util.HashMap; 61 62 /** 63 * Current persistent settings about a display 64 */ 65 class DisplayWindowSettings { 66 private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayWindowSettings" : TAG_WM; 67 68 private static final String SYSTEM_DIRECTORY = "system"; 69 private static final String DISPLAY_SETTINGS_FILE_NAME = "display_settings.xml"; 70 private static final String VENDOR_DISPLAY_SETTINGS_PATH = "etc/" + DISPLAY_SETTINGS_FILE_NAME; 71 private static final String WM_DISPLAY_COMMIT_TAG = "wm-displays"; 72 73 private static final int IDENTIFIER_UNIQUE_ID = 0; 74 private static final int IDENTIFIER_PORT = 1; 75 @IntDef(prefix = { "IDENTIFIER_" }, value = { 76 IDENTIFIER_UNIQUE_ID, 77 IDENTIFIER_PORT, 78 }) 79 @interface DisplayIdentifierType {} 80 81 private final WindowManagerService mService; 82 private final HashMap<String, Entry> mEntries = new HashMap<>(); 83 private final SettingPersister mStorage; 84 85 /** 86 * The preferred type of a display identifier to use when storing and retrieving entries. 87 * {@link #getIdentifier(DisplayInfo)} must be used to get current preferred identifier for each 88 * display. It will fall back to using {@link #IDENTIFIER_UNIQUE_ID} if the currently selected 89 * one is not applicable to a particular display. 90 */ 91 @DisplayIdentifierType 92 private int mIdentifier = IDENTIFIER_UNIQUE_ID; 93 94 /** Interface for persisting the display window settings. */ 95 interface SettingPersister { openRead()96 InputStream openRead() throws IOException; startWrite()97 OutputStream startWrite() throws IOException; finishWrite(OutputStream os, boolean success)98 void finishWrite(OutputStream os, boolean success); 99 } 100 101 private static class Entry { 102 private final String mName; 103 private int mOverscanLeft; 104 private int mOverscanTop; 105 private int mOverscanRight; 106 private int mOverscanBottom; 107 private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED; 108 private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE; 109 private int mUserRotation = Surface.ROTATION_0; 110 private int mForcedWidth; 111 private int mForcedHeight; 112 private int mForcedDensity; 113 private int mForcedScalingMode = FORCE_SCALING_MODE_AUTO; 114 private int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED; 115 private boolean mShouldShowWithInsecureKeyguard = false; 116 private boolean mShouldShowSystemDecors = false; 117 private boolean mShouldShowIme = false; 118 private @DisplayRotation.FixedToUserRotation int mFixedToUserRotation = 119 FIXED_TO_USER_ROTATION_DEFAULT; 120 Entry(String name)121 private Entry(String name) { 122 mName = name; 123 } 124 Entry(String name, Entry copyFrom)125 private Entry(String name, Entry copyFrom) { 126 this(name); 127 mOverscanLeft = copyFrom.mOverscanLeft; 128 mOverscanTop = copyFrom.mOverscanTop; 129 mOverscanRight = copyFrom.mOverscanRight; 130 mOverscanBottom = copyFrom.mOverscanBottom; 131 mWindowingMode = copyFrom.mWindowingMode; 132 mUserRotationMode = copyFrom.mUserRotationMode; 133 mUserRotation = copyFrom.mUserRotation; 134 mForcedWidth = copyFrom.mForcedWidth; 135 mForcedHeight = copyFrom.mForcedHeight; 136 mForcedDensity = copyFrom.mForcedDensity; 137 mForcedScalingMode = copyFrom.mForcedScalingMode; 138 mRemoveContentMode = copyFrom.mRemoveContentMode; 139 mShouldShowWithInsecureKeyguard = copyFrom.mShouldShowWithInsecureKeyguard; 140 mShouldShowSystemDecors = copyFrom.mShouldShowSystemDecors; 141 mShouldShowIme = copyFrom.mShouldShowIme; 142 mFixedToUserRotation = copyFrom.mFixedToUserRotation; 143 } 144 145 /** @return {@code true} if all values are default. */ isEmpty()146 private boolean isEmpty() { 147 return mOverscanLeft == 0 && mOverscanTop == 0 && mOverscanRight == 0 148 && mOverscanBottom == 0 149 && mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED 150 && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE 151 && mUserRotation == Surface.ROTATION_0 152 && mForcedWidth == 0 && mForcedHeight == 0 && mForcedDensity == 0 153 && mForcedScalingMode == FORCE_SCALING_MODE_AUTO 154 && mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED 155 && !mShouldShowWithInsecureKeyguard 156 && !mShouldShowSystemDecors 157 && !mShouldShowIme 158 && mFixedToUserRotation == FIXED_TO_USER_ROTATION_DEFAULT; 159 } 160 } 161 DisplayWindowSettings(WindowManagerService service)162 DisplayWindowSettings(WindowManagerService service) { 163 this(service, new AtomicFileStorage()); 164 } 165 166 @VisibleForTesting DisplayWindowSettings(WindowManagerService service, SettingPersister storageImpl)167 DisplayWindowSettings(WindowManagerService service, SettingPersister storageImpl) { 168 mService = service; 169 mStorage = storageImpl; 170 readSettings(); 171 } 172 getEntry(DisplayInfo displayInfo)173 private @Nullable Entry getEntry(DisplayInfo displayInfo) { 174 final String identifier = getIdentifier(displayInfo); 175 Entry entry; 176 // Try to get corresponding entry using preferred identifier for the current config. 177 if ((entry = mEntries.get(identifier)) != null) { 178 return entry; 179 } 180 // Else, fall back to the display name. 181 if ((entry = mEntries.get(displayInfo.name)) != null) { 182 // Found an entry stored with old identifier - upgrade to the new type now. 183 return updateIdentifierForEntry(entry, displayInfo); 184 } 185 return null; 186 } 187 getOrCreateEntry(DisplayInfo displayInfo)188 private Entry getOrCreateEntry(DisplayInfo displayInfo) { 189 final Entry entry = getEntry(displayInfo); 190 return entry != null ? entry : new Entry(getIdentifier(displayInfo)); 191 } 192 193 /** 194 * Upgrades the identifier of a legacy entry. Does it by copying the data from the old record 195 * and clearing the old key in memory. The entry will be written to storage next time when a 196 * setting changes. 197 */ updateIdentifierForEntry(Entry entry, DisplayInfo displayInfo)198 private Entry updateIdentifierForEntry(Entry entry, DisplayInfo displayInfo) { 199 final Entry newEntry = new Entry(getIdentifier(displayInfo), entry); 200 removeEntry(displayInfo); 201 mEntries.put(newEntry.mName, newEntry); 202 return newEntry; 203 } 204 setOverscanLocked(DisplayInfo displayInfo, int left, int top, int right, int bottom)205 void setOverscanLocked(DisplayInfo displayInfo, int left, int top, int right, int bottom) { 206 final Entry entry = getOrCreateEntry(displayInfo); 207 entry.mOverscanLeft = left; 208 entry.mOverscanTop = top; 209 entry.mOverscanRight = right; 210 entry.mOverscanBottom = bottom; 211 writeSettingsIfNeeded(entry, displayInfo); 212 } 213 setUserRotation(DisplayContent displayContent, int rotationMode, int rotation)214 void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) { 215 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 216 final Entry entry = getOrCreateEntry(displayInfo); 217 entry.mUserRotationMode = rotationMode; 218 entry.mUserRotation = rotation; 219 writeSettingsIfNeeded(entry, displayInfo); 220 } 221 setForcedSize(DisplayContent displayContent, int width, int height)222 void setForcedSize(DisplayContent displayContent, int width, int height) { 223 if (displayContent.isDefaultDisplay) { 224 final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height); 225 Settings.Global.putString(mService.mContext.getContentResolver(), 226 Settings.Global.DISPLAY_SIZE_FORCED, sizeString); 227 return; 228 } 229 230 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 231 final Entry entry = getOrCreateEntry(displayInfo); 232 entry.mForcedWidth = width; 233 entry.mForcedHeight = height; 234 writeSettingsIfNeeded(entry, displayInfo); 235 } 236 setForcedDensity(DisplayContent displayContent, int density, int userId)237 void setForcedDensity(DisplayContent displayContent, int density, int userId) { 238 if (displayContent.isDefaultDisplay) { 239 final String densityString = density == 0 ? "" : Integer.toString(density); 240 Settings.Secure.putStringForUser(mService.mContext.getContentResolver(), 241 Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId); 242 return; 243 } 244 245 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 246 final Entry entry = getOrCreateEntry(displayInfo); 247 entry.mForcedDensity = density; 248 writeSettingsIfNeeded(entry, displayInfo); 249 } 250 setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode)251 void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) { 252 if (displayContent.isDefaultDisplay) { 253 Settings.Global.putInt(mService.mContext.getContentResolver(), 254 Settings.Global.DISPLAY_SCALING_FORCE, mode); 255 return; 256 } 257 258 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 259 final Entry entry = getOrCreateEntry(displayInfo); 260 entry.mForcedScalingMode = mode; 261 writeSettingsIfNeeded(entry, displayInfo); 262 } 263 setFixedToUserRotation(DisplayContent displayContent, @DisplayRotation.FixedToUserRotation int fixedToUserRotation)264 void setFixedToUserRotation(DisplayContent displayContent, 265 @DisplayRotation.FixedToUserRotation int fixedToUserRotation) { 266 final DisplayInfo displayInfo = displayContent.getDisplayInfo(); 267 final Entry entry = getOrCreateEntry(displayInfo); 268 entry.mFixedToUserRotation = fixedToUserRotation; 269 writeSettingsIfNeeded(entry, displayInfo); 270 } 271 getWindowingModeLocked(Entry entry, int displayId)272 private int getWindowingModeLocked(Entry entry, int displayId) { 273 int windowingMode = entry != null ? entry.mWindowingMode 274 : WindowConfiguration.WINDOWING_MODE_UNDEFINED; 275 // This display used to be in freeform, but we don't support freeform anymore, so fall 276 // back to fullscreen. 277 if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM 278 && !mService.mSupportsFreeformWindowManagement) { 279 return WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 280 } 281 // No record is present so use default windowing mode policy. 282 if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) { 283 final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays 284 && displayId != Display.DEFAULT_DISPLAY; 285 windowingMode = mService.mSupportsFreeformWindowManagement 286 && (mService.mIsPc || forceDesktopMode) 287 ? WindowConfiguration.WINDOWING_MODE_FREEFORM 288 : WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 289 } 290 return windowingMode; 291 } 292 getWindowingModeLocked(DisplayContent dc)293 int getWindowingModeLocked(DisplayContent dc) { 294 final DisplayInfo displayInfo = dc.getDisplayInfo(); 295 final Entry entry = getEntry(displayInfo); 296 return getWindowingModeLocked(entry, dc.getDisplayId()); 297 } 298 setWindowingModeLocked(DisplayContent dc, int mode)299 void setWindowingModeLocked(DisplayContent dc, int mode) { 300 final DisplayInfo displayInfo = dc.getDisplayInfo(); 301 final Entry entry = getOrCreateEntry(displayInfo); 302 entry.mWindowingMode = mode; 303 dc.setWindowingMode(mode); 304 writeSettingsIfNeeded(entry, displayInfo); 305 } 306 getRemoveContentModeLocked(DisplayContent dc)307 int getRemoveContentModeLocked(DisplayContent dc) { 308 final DisplayInfo displayInfo = dc.getDisplayInfo(); 309 final Entry entry = getEntry(displayInfo); 310 if (entry == null || entry.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) { 311 if (dc.isPrivate()) { 312 // For private displays by default content is destroyed on removal. 313 return REMOVE_CONTENT_MODE_DESTROY; 314 } 315 // For other displays by default content is moved to primary on removal. 316 return REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY; 317 } 318 return entry.mRemoveContentMode; 319 } 320 setRemoveContentModeLocked(DisplayContent dc, int mode)321 void setRemoveContentModeLocked(DisplayContent dc, int mode) { 322 final DisplayInfo displayInfo = dc.getDisplayInfo(); 323 final Entry entry = getOrCreateEntry(displayInfo); 324 entry.mRemoveContentMode = mode; 325 writeSettingsIfNeeded(entry, displayInfo); 326 } 327 shouldShowWithInsecureKeyguardLocked(DisplayContent dc)328 boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) { 329 final DisplayInfo displayInfo = dc.getDisplayInfo(); 330 final Entry entry = getEntry(displayInfo); 331 if (entry == null) { 332 return false; 333 } 334 return entry.mShouldShowWithInsecureKeyguard; 335 } 336 setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow)337 void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) { 338 if (!dc.isPrivate() && shouldShow) { 339 Slog.e(TAG, "Public display can't be allowed to show content when locked"); 340 return; 341 } 342 343 final DisplayInfo displayInfo = dc.getDisplayInfo(); 344 final Entry entry = getOrCreateEntry(displayInfo); 345 entry.mShouldShowWithInsecureKeyguard = shouldShow; 346 writeSettingsIfNeeded(entry, displayInfo); 347 } 348 shouldShowSystemDecorsLocked(DisplayContent dc)349 boolean shouldShowSystemDecorsLocked(DisplayContent dc) { 350 if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) { 351 // For default display should show system decors. 352 return true; 353 } 354 355 final DisplayInfo displayInfo = dc.getDisplayInfo(); 356 final Entry entry = getEntry(displayInfo); 357 if (entry == null) { 358 return false; 359 } 360 return entry.mShouldShowSystemDecors; 361 } 362 setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow)363 void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) { 364 if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) { 365 Slog.e(TAG, "Default display should show system decors"); 366 return; 367 } 368 369 final DisplayInfo displayInfo = dc.getDisplayInfo(); 370 final Entry entry = getOrCreateEntry(displayInfo); 371 entry.mShouldShowSystemDecors = shouldShow; 372 writeSettingsIfNeeded(entry, displayInfo); 373 } 374 shouldShowImeLocked(DisplayContent dc)375 boolean shouldShowImeLocked(DisplayContent dc) { 376 if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) { 377 // For default display should shows IME. 378 return true; 379 } 380 381 final DisplayInfo displayInfo = dc.getDisplayInfo(); 382 final Entry entry = getEntry(displayInfo); 383 if (entry == null) { 384 return false; 385 } 386 return entry.mShouldShowIme; 387 } 388 setShouldShowImeLocked(DisplayContent dc, boolean shouldShow)389 void setShouldShowImeLocked(DisplayContent dc, boolean shouldShow) { 390 if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) { 391 Slog.e(TAG, "Default display should show IME"); 392 return; 393 } 394 395 final DisplayInfo displayInfo = dc.getDisplayInfo(); 396 final Entry entry = getOrCreateEntry(displayInfo); 397 entry.mShouldShowIme = shouldShow; 398 writeSettingsIfNeeded(entry, displayInfo); 399 } 400 applySettingsToDisplayLocked(DisplayContent dc)401 void applySettingsToDisplayLocked(DisplayContent dc) { 402 final DisplayInfo displayInfo = dc.getDisplayInfo(); 403 final Entry entry = getOrCreateEntry(displayInfo); 404 405 // Setting windowing mode first, because it may override overscan values later. 406 dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId())); 407 408 displayInfo.overscanLeft = entry.mOverscanLeft; 409 displayInfo.overscanTop = entry.mOverscanTop; 410 displayInfo.overscanRight = entry.mOverscanRight; 411 displayInfo.overscanBottom = entry.mOverscanBottom; 412 413 dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode, 414 entry.mUserRotation, entry.mFixedToUserRotation); 415 416 if (entry.mForcedDensity != 0) { 417 dc.mBaseDisplayDensity = entry.mForcedDensity; 418 } 419 if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) { 420 dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight, 421 dc.mBaseDisplayDensity); 422 } 423 dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED; 424 } 425 426 /** 427 * Updates settings for the given display after system features are loaded into window manager 428 * service, e.g. if this device is PC and if this device supports freeform. 429 * 430 * @param dc the given display. 431 * @return {@code true} if any settings for this display has changed; {@code false} if nothing 432 * changed. 433 */ updateSettingsForDisplay(DisplayContent dc)434 boolean updateSettingsForDisplay(DisplayContent dc) { 435 if (dc.getWindowingMode() != getWindowingModeLocked(dc)) { 436 // For the time being the only thing that may change is windowing mode, so just update 437 // that. 438 dc.setWindowingMode(getWindowingModeLocked(dc)); 439 return true; 440 } 441 return false; 442 } 443 readSettings()444 private void readSettings() { 445 InputStream stream; 446 try { 447 stream = mStorage.openRead(); 448 } catch (IOException e) { 449 Slog.i(TAG, "No existing display settings, starting empty"); 450 return; 451 } 452 boolean success = false; 453 try { 454 XmlPullParser parser = Xml.newPullParser(); 455 parser.setInput(stream, StandardCharsets.UTF_8.name()); 456 int type; 457 while ((type = parser.next()) != XmlPullParser.START_TAG 458 && type != XmlPullParser.END_DOCUMENT) { 459 // Do nothing. 460 } 461 462 if (type != XmlPullParser.START_TAG) { 463 throw new IllegalStateException("no start tag found"); 464 } 465 466 int outerDepth = parser.getDepth(); 467 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 468 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 469 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 470 continue; 471 } 472 473 String tagName = parser.getName(); 474 if (tagName.equals("display")) { 475 readDisplay(parser); 476 } else if (tagName.equals("config")) { 477 readConfig(parser); 478 } else { 479 Slog.w(TAG, "Unknown element under <display-settings>: " 480 + parser.getName()); 481 XmlUtils.skipCurrentTag(parser); 482 } 483 } 484 success = true; 485 } catch (IllegalStateException e) { 486 Slog.w(TAG, "Failed parsing " + e); 487 } catch (NullPointerException e) { 488 Slog.w(TAG, "Failed parsing " + e); 489 } catch (NumberFormatException e) { 490 Slog.w(TAG, "Failed parsing " + e); 491 } catch (XmlPullParserException e) { 492 Slog.w(TAG, "Failed parsing " + e); 493 } catch (IOException e) { 494 Slog.w(TAG, "Failed parsing " + e); 495 } catch (IndexOutOfBoundsException e) { 496 Slog.w(TAG, "Failed parsing " + e); 497 } finally { 498 if (!success) { 499 mEntries.clear(); 500 } 501 try { 502 stream.close(); 503 } catch (IOException e) { 504 } 505 } 506 } 507 getIntAttribute(XmlPullParser parser, String name)508 private int getIntAttribute(XmlPullParser parser, String name) { 509 return getIntAttribute(parser, name, 0 /* defaultValue */); 510 } 511 getIntAttribute(XmlPullParser parser, String name, int defaultValue)512 private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) { 513 try { 514 final String str = parser.getAttributeValue(null, name); 515 return str != null ? Integer.parseInt(str) : defaultValue; 516 } catch (NumberFormatException e) { 517 return defaultValue; 518 } 519 } 520 getBooleanAttribute(XmlPullParser parser, String name)521 private boolean getBooleanAttribute(XmlPullParser parser, String name) { 522 return getBooleanAttribute(parser, name, false /* defaultValue */); 523 } 524 getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue)525 private boolean getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue) { 526 try { 527 final String str = parser.getAttributeValue(null, name); 528 return str != null ? Boolean.parseBoolean(str) : defaultValue; 529 } catch (NumberFormatException e) { 530 return defaultValue; 531 } 532 } 533 readDisplay(XmlPullParser parser)534 private void readDisplay(XmlPullParser parser) throws NumberFormatException, 535 XmlPullParserException, IOException { 536 String name = parser.getAttributeValue(null, "name"); 537 if (name != null) { 538 Entry entry = new Entry(name); 539 entry.mOverscanLeft = getIntAttribute(parser, "overscanLeft"); 540 entry.mOverscanTop = getIntAttribute(parser, "overscanTop"); 541 entry.mOverscanRight = getIntAttribute(parser, "overscanRight"); 542 entry.mOverscanBottom = getIntAttribute(parser, "overscanBottom"); 543 entry.mWindowingMode = getIntAttribute(parser, "windowingMode", 544 WindowConfiguration.WINDOWING_MODE_UNDEFINED); 545 entry.mUserRotationMode = getIntAttribute(parser, "userRotationMode", 546 WindowManagerPolicy.USER_ROTATION_FREE); 547 entry.mUserRotation = getIntAttribute(parser, "userRotation", 548 Surface.ROTATION_0); 549 entry.mForcedWidth = getIntAttribute(parser, "forcedWidth"); 550 entry.mForcedHeight = getIntAttribute(parser, "forcedHeight"); 551 entry.mForcedDensity = getIntAttribute(parser, "forcedDensity"); 552 entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode", 553 FORCE_SCALING_MODE_AUTO); 554 entry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode", 555 REMOVE_CONTENT_MODE_UNDEFINED); 556 entry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser, 557 "shouldShowWithInsecureKeyguard"); 558 entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors"); 559 entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme"); 560 entry.mFixedToUserRotation = getIntAttribute(parser, "fixedToUserRotation"); 561 mEntries.put(name, entry); 562 } 563 XmlUtils.skipCurrentTag(parser); 564 } 565 readConfig(XmlPullParser parser)566 private void readConfig(XmlPullParser parser) throws NumberFormatException, 567 XmlPullParserException, IOException { 568 mIdentifier = getIntAttribute(parser, "identifier"); 569 XmlUtils.skipCurrentTag(parser); 570 } 571 writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo)572 private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) { 573 if (changedEntry.isEmpty() && !removeEntry(displayInfo)) { 574 // The entry didn't exist so nothing is changed and no need to update the file. 575 return; 576 } 577 578 mEntries.put(getIdentifier(displayInfo), changedEntry); 579 writeSettings(); 580 } 581 writeSettings()582 private void writeSettings() { 583 OutputStream stream; 584 try { 585 stream = mStorage.startWrite(); 586 } catch (IOException e) { 587 Slog.w(TAG, "Failed to write display settings: " + e); 588 return; 589 } 590 591 try { 592 XmlSerializer out = new FastXmlSerializer(); 593 out.setOutput(stream, StandardCharsets.UTF_8.name()); 594 out.startDocument(null, true); 595 596 out.startTag(null, "display-settings"); 597 598 out.startTag(null, "config"); 599 out.attribute(null, "identifier", Integer.toString(mIdentifier)); 600 out.endTag(null, "config"); 601 602 for (Entry entry : mEntries.values()) { 603 out.startTag(null, "display"); 604 out.attribute(null, "name", entry.mName); 605 if (entry.mOverscanLeft != 0) { 606 out.attribute(null, "overscanLeft", Integer.toString(entry.mOverscanLeft)); 607 } 608 if (entry.mOverscanTop != 0) { 609 out.attribute(null, "overscanTop", Integer.toString(entry.mOverscanTop)); 610 } 611 if (entry.mOverscanRight != 0) { 612 out.attribute(null, "overscanRight", Integer.toString(entry.mOverscanRight)); 613 } 614 if (entry.mOverscanBottom != 0) { 615 out.attribute(null, "overscanBottom", Integer.toString(entry.mOverscanBottom)); 616 } 617 if (entry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) { 618 out.attribute(null, "windowingMode", Integer.toString(entry.mWindowingMode)); 619 } 620 if (entry.mUserRotationMode != WindowManagerPolicy.USER_ROTATION_FREE) { 621 out.attribute(null, "userRotationMode", 622 Integer.toString(entry.mUserRotationMode)); 623 } 624 if (entry.mUserRotation != Surface.ROTATION_0) { 625 out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation)); 626 } 627 if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) { 628 out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth)); 629 out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight)); 630 } 631 if (entry.mForcedDensity != 0) { 632 out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity)); 633 } 634 if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) { 635 out.attribute(null, "forcedScalingMode", 636 Integer.toString(entry.mForcedScalingMode)); 637 } 638 if (entry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) { 639 out.attribute(null, "removeContentMode", 640 Integer.toString(entry.mRemoveContentMode)); 641 } 642 if (entry.mShouldShowWithInsecureKeyguard) { 643 out.attribute(null, "shouldShowWithInsecureKeyguard", 644 Boolean.toString(entry.mShouldShowWithInsecureKeyguard)); 645 } 646 if (entry.mShouldShowSystemDecors) { 647 out.attribute(null, "shouldShowSystemDecors", 648 Boolean.toString(entry.mShouldShowSystemDecors)); 649 } 650 if (entry.mShouldShowIme) { 651 out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme)); 652 } 653 if (entry.mFixedToUserRotation != FIXED_TO_USER_ROTATION_DEFAULT) { 654 out.attribute(null, "fixedToUserRotation", 655 Integer.toString(entry.mFixedToUserRotation)); 656 } 657 out.endTag(null, "display"); 658 } 659 660 out.endTag(null, "display-settings"); 661 out.endDocument(); 662 mStorage.finishWrite(stream, true /* success */); 663 } catch (IOException e) { 664 Slog.w(TAG, "Failed to write display window settings.", e); 665 mStorage.finishWrite(stream, false /* success */); 666 } 667 } 668 669 /** 670 * Removes an entry from {@link #mEntries} cache. Looks up by new and previously used 671 * identifiers. 672 */ removeEntry(DisplayInfo displayInfo)673 private boolean removeEntry(DisplayInfo displayInfo) { 674 // Remove entry based on primary identifier. 675 boolean removed = mEntries.remove(getIdentifier(displayInfo)) != null; 676 // Ensure that legacy entries are cleared as well. 677 removed |= mEntries.remove(displayInfo.uniqueId) != null; 678 removed |= mEntries.remove(displayInfo.name) != null; 679 return removed; 680 } 681 682 /** Gets the identifier of choice for the current config. */ getIdentifier(DisplayInfo displayInfo)683 private String getIdentifier(DisplayInfo displayInfo) { 684 if (mIdentifier == IDENTIFIER_PORT && displayInfo.address != null) { 685 // Config suggests using port as identifier for physical displays. 686 if (displayInfo.address instanceof DisplayAddress.Physical) { 687 return "port:" + ((DisplayAddress.Physical) displayInfo.address).getPort(); 688 } 689 } 690 return displayInfo.uniqueId; 691 } 692 693 private static class AtomicFileStorage implements SettingPersister { 694 private final AtomicFile mAtomicFile; 695 AtomicFileStorage()696 AtomicFileStorage() { 697 final File folder = new File(Environment.getDataDirectory(), SYSTEM_DIRECTORY); 698 final File settingsFile = new File(folder, DISPLAY_SETTINGS_FILE_NAME); 699 // If display_settings.xml doesn't exist, try to copy the vendor's one instead 700 // in order to provide the vendor specific initialization. 701 if (!settingsFile.exists()) { 702 copyVendorSettings(settingsFile); 703 } 704 mAtomicFile = new AtomicFile(settingsFile, WM_DISPLAY_COMMIT_TAG); 705 } 706 copyVendorSettings(File target)707 private static void copyVendorSettings(File target) { 708 final File vendorFile = new File(Environment.getVendorDirectory(), 709 VENDOR_DISPLAY_SETTINGS_PATH); 710 if (vendorFile.canRead()) { 711 try { 712 FileUtils.copy(vendorFile, target); 713 } catch (IOException e) { 714 Slog.e(TAG, "Failed to copy vendor display_settings.xml"); 715 } 716 } 717 } 718 719 @Override openRead()720 public InputStream openRead() throws FileNotFoundException { 721 return mAtomicFile.openRead(); 722 } 723 724 @Override startWrite()725 public OutputStream startWrite() throws IOException { 726 return mAtomicFile.startWrite(); 727 } 728 729 @Override finishWrite(OutputStream os, boolean success)730 public void finishWrite(OutputStream os, boolean success) { 731 if (!(os instanceof FileOutputStream)) { 732 throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os); 733 } 734 FileOutputStream fos = (FileOutputStream) os; 735 if (success) { 736 mAtomicFile.finishWrite(fos); 737 } else { 738 mAtomicFile.failWrite(fos); 739 } 740 } 741 } 742 } 743