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