content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wm;
  
  import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
  import static android.view.WindowManager.REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
  import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
  
  import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO;
  import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED;
  import static com.android.server.wm.DisplayRotation.FIXED_TO_USER_ROTATION_DEFAULT;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
  import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
  
  import android.annotation.IntDef;
  import android.annotation.Nullable;
  import android.app.WindowConfiguration;
  import android.os.Environment;
  import android.os.FileUtils;
  import android.provider.Settings;
  import android.util.AtomicFile;
  import android.util.Slog;
  import android.util.Xml;
  import android.view.Display;
  import android.view.DisplayAddress;
  import android.view.DisplayInfo;
  import android.view.Surface;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.FastXmlSerializer;
  import com.android.internal.util.XmlUtils;
  import com.android.server.policy.WindowManagerPolicy;
  import com.android.server.wm.DisplayContent.ForceScalingMode;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  import org.xmlpull.v1.XmlSerializer;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.nio.charset.StandardCharsets;
  import java.util.HashMap;
  
  /**
   * Current persistent settings about a display
   */
  class DisplayWindowSettings {
      private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayWindowSettings" : TAG_WM;
  
      private static final String SYSTEM_DIRECTORY = "system";
      private static final String DISPLAY_SETTINGS_FILE_NAME = "display_settings.xml";
      private static final String VENDOR_DISPLAY_SETTINGS_PATH = "etc/" + DISPLAY_SETTINGS_FILE_NAME;
      private static final String WM_DISPLAY_COMMIT_TAG = "wm-displays";
  
      private static final int IDENTIFIER_UNIQUE_ID = 0;
      private static final int IDENTIFIER_PORT = 1;
      @IntDef(prefix = { "IDENTIFIER_" }, value = {
              IDENTIFIER_UNIQUE_ID,
              IDENTIFIER_PORT,
      })
      @interface DisplayIdentifierType {}
  
      private final WindowManagerService mService;
      private final HashMap<String, Entry> mEntries = new HashMap<>();
      private final SettingPersister mStorage;
  
      /**
       * The preferred type of a display identifier to use when storing and retrieving entries.
       * {@link #getIdentifier(DisplayInfo)} must be used to get current preferred identifier for each
       * display. It will fall back to using {@link #IDENTIFIER_UNIQUE_ID} if the currently selected
       * one is not applicable to a particular display.
       */
      @DisplayIdentifierType
      private int mIdentifier = IDENTIFIER_UNIQUE_ID;
  
      /** Interface for persisting the display window settings. */
      interface SettingPersister {
          InputStream openRead() throws IOException;
          OutputStream startWrite() throws IOException;
          void finishWrite(OutputStream os, boolean success);
      }
  
      private static class Entry {
          private final String mName;
          private int mOverscanLeft;
          private int mOverscanTop;
          private int mOverscanRight;
          private int mOverscanBottom;
          private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED;
          private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
          private int mUserRotation = Surface.ROTATION_0;
          private int mForcedWidth;
          private int mForcedHeight;
          private int mForcedDensity;
          private int mForcedScalingMode = FORCE_SCALING_MODE_AUTO;
          private int mRemoveContentMode = REMOVE_CONTENT_MODE_UNDEFINED;
          private boolean mShouldShowWithInsecureKeyguard = false;
          private boolean mShouldShowSystemDecors = false;
          private boolean mShouldShowIme = false;
          private @DisplayRotation.FixedToUserRotation int mFixedToUserRotation =
                  FIXED_TO_USER_ROTATION_DEFAULT;
  
          private Entry(String name) {
              mName = name;
          }
  
          private Entry(String name, Entry copyFrom) {
              this(name);
              mOverscanLeft = copyFrom.mOverscanLeft;
              mOverscanTop = copyFrom.mOverscanTop;
              mOverscanRight = copyFrom.mOverscanRight;
              mOverscanBottom = copyFrom.mOverscanBottom;
              mWindowingMode = copyFrom.mWindowingMode;
              mUserRotationMode = copyFrom.mUserRotationMode;
              mUserRotation = copyFrom.mUserRotation;
              mForcedWidth = copyFrom.mForcedWidth;
              mForcedHeight = copyFrom.mForcedHeight;
              mForcedDensity = copyFrom.mForcedDensity;
              mForcedScalingMode = copyFrom.mForcedScalingMode;
              mRemoveContentMode = copyFrom.mRemoveContentMode;
              mShouldShowWithInsecureKeyguard = copyFrom.mShouldShowWithInsecureKeyguard;
              mShouldShowSystemDecors = copyFrom.mShouldShowSystemDecors;
              mShouldShowIme = copyFrom.mShouldShowIme;
              mFixedToUserRotation = copyFrom.mFixedToUserRotation;
          }
  
          /** @return {@code true} if all values are default. */
          private boolean isEmpty() {
              return mOverscanLeft == 0 && mOverscanTop == 0 && mOverscanRight == 0
                      && mOverscanBottom == 0
                      && mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED
                      && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
                      && mUserRotation == Surface.ROTATION_0
                      && mForcedWidth == 0 && mForcedHeight == 0 && mForcedDensity == 0
                      && mForcedScalingMode == FORCE_SCALING_MODE_AUTO
                      && mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED
                      && !mShouldShowWithInsecureKeyguard
                      && !mShouldShowSystemDecors
                      && !mShouldShowIme
                      && mFixedToUserRotation == FIXED_TO_USER_ROTATION_DEFAULT;
          }
      }
  
      DisplayWindowSettings(WindowManagerService service) {
          this(service, new AtomicFileStorage());
      }
  
      @VisibleForTesting
      DisplayWindowSettings(WindowManagerService service, SettingPersister storageImpl) {
          mService = service;
          mStorage = storageImpl;
          readSettings();
      }
  
      private @Nullable Entry getEntry(DisplayInfo displayInfo) {
          final String identifier = getIdentifier(displayInfo);
          Entry entry;
          // Try to get corresponding entry using preferred identifier for the current config.
          if ((entry = mEntries.get(identifier)) != null) {
              return entry;
          }
          // Else, fall back to the display name.
          if ((entry = mEntries.get(displayInfo.name)) != null) {
              // Found an entry stored with old identifier - upgrade to the new type now.
              return updateIdentifierForEntry(entry, displayInfo);
          }
          return null;
      }
  
      private Entry getOrCreateEntry(DisplayInfo displayInfo) {
          final Entry entry = getEntry(displayInfo);
          return entry != null ? entry : new Entry(getIdentifier(displayInfo));
      }
  
      /**
       * Upgrades the identifier of a legacy entry. Does it by copying the data from the old record
       * and clearing the old key in memory. The entry will be written to storage next time when a
       * setting changes.
       */
      private Entry updateIdentifierForEntry(Entry entry, DisplayInfo displayInfo) {
          final Entry newEntry = new Entry(getIdentifier(displayInfo), entry);
          removeEntry(displayInfo);
          mEntries.put(newEntry.mName, newEntry);
          return newEntry;
      }
  
      void setOverscanLocked(DisplayInfo displayInfo, int left, int top, int right, int bottom) {
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mOverscanLeft = left;
          entry.mOverscanTop = top;
          entry.mOverscanRight = right;
          entry.mOverscanBottom = bottom;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) {
          final DisplayInfo displayInfo = displayContent.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mUserRotationMode = rotationMode;
          entry.mUserRotation = rotation;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void setForcedSize(DisplayContent displayContent, int width, int height) {
          if (displayContent.isDefaultDisplay) {
              final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height);
              Settings.Global.putString(mService.mContext.getContentResolver(),
                      Settings.Global.DISPLAY_SIZE_FORCED, sizeString);
              return;
          }
  
          final DisplayInfo displayInfo = displayContent.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mForcedWidth = width;
          entry.mForcedHeight = height;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void setForcedDensity(DisplayContent displayContent, int density, int userId) {
          if (displayContent.isDefaultDisplay) {
              final String densityString = density == 0 ? "" : Integer.toString(density);
              Settings.Secure.putStringForUser(mService.mContext.getContentResolver(),
                      Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId);
              return;
          }
  
          final DisplayInfo displayInfo = displayContent.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mForcedDensity = density;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) {
          if (displayContent.isDefaultDisplay) {
              Settings.Global.putInt(mService.mContext.getContentResolver(),
                      Settings.Global.DISPLAY_SCALING_FORCE, mode);
              return;
          }
  
          final DisplayInfo displayInfo = displayContent.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mForcedScalingMode = mode;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void setFixedToUserRotation(DisplayContent displayContent,
              @DisplayRotation.FixedToUserRotation int fixedToUserRotation) {
          final DisplayInfo displayInfo = displayContent.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mFixedToUserRotation = fixedToUserRotation;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      private int getWindowingModeLocked(Entry entry, int displayId) {
          int windowingMode = entry != null ? entry.mWindowingMode
                  : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
          // This display used to be in freeform, but we don't support freeform anymore, so fall
          // back to fullscreen.
          if (windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
                  && !mService.mSupportsFreeformWindowManagement) {
              return WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
          }
          // No record is present so use default windowing mode policy.
          if (windowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
              final boolean forceDesktopMode = mService.mForceDesktopModeOnExternalDisplays
                      && displayId != Display.DEFAULT_DISPLAY;
              windowingMode = mService.mSupportsFreeformWindowManagement
                      && (mService.mIsPc || forceDesktopMode)
                      ? WindowConfiguration.WINDOWING_MODE_FREEFORM
                      : WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
          }
          return windowingMode;
      }
  
      int getWindowingModeLocked(DisplayContent dc) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getEntry(displayInfo);
          return getWindowingModeLocked(entry, dc.getDisplayId());
      }
  
      void setWindowingModeLocked(DisplayContent dc, int mode) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mWindowingMode = mode;
          dc.setWindowingMode(mode);
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      int getRemoveContentModeLocked(DisplayContent dc) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getEntry(displayInfo);
          if (entry == null || entry.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
              if (dc.isPrivate()) {
                  // For private displays by default content is destroyed on removal.
                  return REMOVE_CONTENT_MODE_DESTROY;
              }
              // For other displays by default content is moved to primary on removal.
              return REMOVE_CONTENT_MODE_MOVE_TO_PRIMARY;
          }
          return entry.mRemoveContentMode;
      }
  
      void setRemoveContentModeLocked(DisplayContent dc, int mode) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mRemoveContentMode = mode;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      boolean shouldShowWithInsecureKeyguardLocked(DisplayContent dc) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getEntry(displayInfo);
          if (entry == null) {
              return false;
          }
          return entry.mShouldShowWithInsecureKeyguard;
      }
  
      void setShouldShowWithInsecureKeyguardLocked(DisplayContent dc, boolean shouldShow) {
          if (!dc.isPrivate() && shouldShow) {
              Slog.e(TAG, "Public display can't be allowed to show content when locked");
              return;
          }
  
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mShouldShowWithInsecureKeyguard = shouldShow;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      boolean shouldShowSystemDecorsLocked(DisplayContent dc) {
          if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
              // For default display should show system decors.
              return true;
          }
  
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getEntry(displayInfo);
          if (entry == null) {
              return false;
          }
          return entry.mShouldShowSystemDecors;
      }
  
      void setShouldShowSystemDecorsLocked(DisplayContent dc, boolean shouldShow) {
          if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
              Slog.e(TAG, "Default display should show system decors");
              return;
          }
  
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mShouldShowSystemDecors = shouldShow;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      boolean shouldShowImeLocked(DisplayContent dc) {
          if (dc.getDisplayId() == Display.DEFAULT_DISPLAY) {
              // For default display should shows IME.
              return true;
          }
  
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getEntry(displayInfo);
          if (entry == null) {
              return false;
          }
          return entry.mShouldShowIme;
      }
  
      void setShouldShowImeLocked(DisplayContent dc, boolean shouldShow) {
          if (dc.getDisplayId() == Display.DEFAULT_DISPLAY && !shouldShow) {
              Slog.e(TAG, "Default display should show IME");
              return;
          }
  
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
          entry.mShouldShowIme = shouldShow;
          writeSettingsIfNeeded(entry, displayInfo);
      }
  
      void applySettingsToDisplayLocked(DisplayContent dc) {
          final DisplayInfo displayInfo = dc.getDisplayInfo();
          final Entry entry = getOrCreateEntry(displayInfo);
  
          // Setting windowing mode first, because it may override overscan values later.
          dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId()));
  
          displayInfo.overscanLeft = entry.mOverscanLeft;
          displayInfo.overscanTop = entry.mOverscanTop;
          displayInfo.overscanRight = entry.mOverscanRight;
          displayInfo.overscanBottom = entry.mOverscanBottom;
  
          dc.getDisplayRotation().restoreSettings(entry.mUserRotationMode,
                  entry.mUserRotation, entry.mFixedToUserRotation);
  
          if (entry.mForcedDensity != 0) {
              dc.mBaseDisplayDensity = entry.mForcedDensity;
          }
          if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
              dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight,
                      dc.mBaseDisplayDensity);
          }
          dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED;
      }
  
      /**
       * Updates settings for the given display after system features are loaded into window manager
       * service, e.g. if this device is PC and if this device supports freeform.
       *
       * @param dc the given display.
       * @return {@code true} if any settings for this display has changed; {@code false} if nothing
       * changed.
       */
      boolean updateSettingsForDisplay(DisplayContent dc) {
          if (dc.getWindowingMode() != getWindowingModeLocked(dc)) {
              // For the time being the only thing that may change is windowing mode, so just update
              // that.
              dc.setWindowingMode(getWindowingModeLocked(dc));
              return true;
          }
          return false;
      }
  
      private void readSettings() {
          InputStream stream;
          try {
              stream = mStorage.openRead();
          } catch (IOException e) {
              Slog.i(TAG, "No existing display settings, starting empty");
              return;
          }
          boolean success = false;
          try {
              XmlPullParser parser = Xml.newPullParser();
              parser.setInput(stream, StandardCharsets.UTF_8.name());
              int type;
              while ((type = parser.next()) != XmlPullParser.START_TAG
                      && type != XmlPullParser.END_DOCUMENT) {
                  // Do nothing.
              }
  
              if (type != XmlPullParser.START_TAG) {
                  throw new IllegalStateException("no start tag found");
              }
  
              int outerDepth = parser.getDepth();
              while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                      && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                  if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                      continue;
                  }
  
                  String tagName = parser.getName();
                  if (tagName.equals("display")) {
                      readDisplay(parser);
                  } else if (tagName.equals("config")) {
                      readConfig(parser);
                  } else {
                      Slog.w(TAG, "Unknown element under <display-settings>: "
                              + parser.getName());
                      XmlUtils.skipCurrentTag(parser);
                  }
              }
              success = true;
          } catch (IllegalStateException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } catch (NullPointerException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } catch (NumberFormatException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } catch (XmlPullParserException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } catch (IOException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } catch (IndexOutOfBoundsException e) {
              Slog.w(TAG, "Failed parsing " + e);
          } finally {
              if (!success) {
                  mEntries.clear();
              }
              try {
                  stream.close();
              } catch (IOException e) {
              }
          }
      }
  
      private int getIntAttribute(XmlPullParser parser, String name) {
          return getIntAttribute(parser, name, 0 /* defaultValue */);
      }
  
      private int getIntAttribute(XmlPullParser parser, String name, int defaultValue) {
          try {
              final String str = parser.getAttributeValue(null, name);
              return str != null ? Integer.parseInt(str) : defaultValue;
          } catch (NumberFormatException e) {
              return defaultValue;
          }
      }
  
      private boolean getBooleanAttribute(XmlPullParser parser, String name) {
          return getBooleanAttribute(parser, name, false /* defaultValue */);
      }
  
      private boolean getBooleanAttribute(XmlPullParser parser, String name, boolean defaultValue) {
          try {
              final String str = parser.getAttributeValue(null, name);
              return str != null ? Boolean.parseBoolean(str) : defaultValue;
          } catch (NumberFormatException e) {
              return defaultValue;
          }
      }
  
      private void readDisplay(XmlPullParser parser) throws NumberFormatException,
              XmlPullParserException, IOException {
          String name = parser.getAttributeValue(null, "name");
          if (name != null) {
              Entry entry = new Entry(name);
              entry.mOverscanLeft = getIntAttribute(parser, "overscanLeft");
              entry.mOverscanTop = getIntAttribute(parser, "overscanTop");
              entry.mOverscanRight = getIntAttribute(parser, "overscanRight");
              entry.mOverscanBottom = getIntAttribute(parser, "overscanBottom");
              entry.mWindowingMode = getIntAttribute(parser, "windowingMode",
                      WindowConfiguration.WINDOWING_MODE_UNDEFINED);
              entry.mUserRotationMode = getIntAttribute(parser, "userRotationMode",
                      WindowManagerPolicy.USER_ROTATION_FREE);
              entry.mUserRotation = getIntAttribute(parser, "userRotation",
                      Surface.ROTATION_0);
              entry.mForcedWidth = getIntAttribute(parser, "forcedWidth");
              entry.mForcedHeight = getIntAttribute(parser, "forcedHeight");
              entry.mForcedDensity = getIntAttribute(parser, "forcedDensity");
              entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode",
                      FORCE_SCALING_MODE_AUTO);
              entry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",
                      REMOVE_CONTENT_MODE_UNDEFINED);
              entry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,
                      "shouldShowWithInsecureKeyguard");
              entry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors");
              entry.mShouldShowIme = getBooleanAttribute(parser, "shouldShowIme");
              entry.mFixedToUserRotation = getIntAttribute(parser, "fixedToUserRotation");
              mEntries.put(name, entry);
          }
          XmlUtils.skipCurrentTag(parser);
      }
  
      private void readConfig(XmlPullParser parser) throws NumberFormatException,
              XmlPullParserException, IOException {
          mIdentifier = getIntAttribute(parser, "identifier");
          XmlUtils.skipCurrentTag(parser);
      }
  
      private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) {
          if (changedEntry.isEmpty() && !removeEntry(displayInfo)) {
              // The entry didn't exist so nothing is changed and no need to update the file.
              return;
          }
  
          mEntries.put(getIdentifier(displayInfo), changedEntry);
          writeSettings();
      }
  
      private void writeSettings() {
          OutputStream stream;
          try {
              stream = mStorage.startWrite();
          } catch (IOException e) {
              Slog.w(TAG, "Failed to write display settings: " + e);
              return;
          }
  
          try {
              XmlSerializer out = new FastXmlSerializer();
              out.setOutput(stream, StandardCharsets.UTF_8.name());
              out.startDocument(null, true);
  
              out.startTag(null, "display-settings");
  
              out.startTag(null, "config");
              out.attribute(null, "identifier", Integer.toString(mIdentifier));
              out.endTag(null, "config");
  
              for (Entry entry : mEntries.values()) {
                  out.startTag(null, "display");
                  out.attribute(null, "name", entry.mName);
                  if (entry.mOverscanLeft != 0) {
                      out.attribute(null, "overscanLeft", Integer.toString(entry.mOverscanLeft));
                  }
                  if (entry.mOverscanTop != 0) {
                      out.attribute(null, "overscanTop", Integer.toString(entry.mOverscanTop));
                  }
                  if (entry.mOverscanRight != 0) {
                      out.attribute(null, "overscanRight", Integer.toString(entry.mOverscanRight));
                  }
                  if (entry.mOverscanBottom != 0) {
                      out.attribute(null, "overscanBottom", Integer.toString(entry.mOverscanBottom));
                  }
                  if (entry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
                      out.attribute(null, "windowingMode", Integer.toString(entry.mWindowingMode));
                  }
                  if (entry.mUserRotationMode != WindowManagerPolicy.USER_ROTATION_FREE) {
                      out.attribute(null, "userRotationMode",
                              Integer.toString(entry.mUserRotationMode));
                  }
                  if (entry.mUserRotation != Surface.ROTATION_0) {
                      out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation));
                  }
                  if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) {
                      out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth));
                      out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight));
                  }
                  if (entry.mForcedDensity != 0) {
                      out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity));
                  }
                  if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) {
                      out.attribute(null, "forcedScalingMode",
                              Integer.toString(entry.mForcedScalingMode));
                  }
                  if (entry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) {
                      out.attribute(null, "removeContentMode",
                              Integer.toString(entry.mRemoveContentMode));
                  }
                  if (entry.mShouldShowWithInsecureKeyguard) {
                      out.attribute(null, "shouldShowWithInsecureKeyguard",
                              Boolean.toString(entry.mShouldShowWithInsecureKeyguard));
                  }
                  if (entry.mShouldShowSystemDecors) {
                      out.attribute(null, "shouldShowSystemDecors",
                              Boolean.toString(entry.mShouldShowSystemDecors));
                  }
                  if (entry.mShouldShowIme) {
                      out.attribute(null, "shouldShowIme", Boolean.toString(entry.mShouldShowIme));
                  }
                  if (entry.mFixedToUserRotation != FIXED_TO_USER_ROTATION_DEFAULT) {
                      out.attribute(null, "fixedToUserRotation",
                              Integer.toString(entry.mFixedToUserRotation));
                  }
                  out.endTag(null, "display");
              }
  
              out.endTag(null, "display-settings");
              out.endDocument();
              mStorage.finishWrite(stream, true /* success */);
          } catch (IOException e) {
              Slog.w(TAG, "Failed to write display window settings.", e);
              mStorage.finishWrite(stream, false /* success */);
          }
      }
  
      /**
       * Removes an entry from {@link #mEntries} cache. Looks up by new and previously used
       * identifiers.
       */
      private boolean removeEntry(DisplayInfo displayInfo) {
          // Remove entry based on primary identifier.
          boolean removed = mEntries.remove(getIdentifier(displayInfo)) != null;
          // Ensure that legacy entries are cleared as well.
          removed |= mEntries.remove(displayInfo.uniqueId) != null;
          removed |= mEntries.remove(displayInfo.name) != null;
          return removed;
      }
  
      /** Gets the identifier of choice for the current config. */
      private String getIdentifier(DisplayInfo displayInfo) {
          if (mIdentifier == IDENTIFIER_PORT && displayInfo.address != null) {
              // Config suggests using port as identifier for physical displays.
              if (displayInfo.address instanceof DisplayAddress.Physical) {
                  return "port:" + ((DisplayAddress.Physical) displayInfo.address).getPort();
              }
          }
          return displayInfo.uniqueId;
      }
  
      private static class AtomicFileStorage implements SettingPersister {
          private final AtomicFile mAtomicFile;
  
          AtomicFileStorage() {
              final File folder = new File(Environment.getDataDirectory(), SYSTEM_DIRECTORY);
              final File settingsFile = new File(folder, DISPLAY_SETTINGS_FILE_NAME);
              // If display_settings.xml doesn't exist, try to copy the vendor's one instead
              // in order to provide the vendor specific initialization.
              if (!settingsFile.exists()) {
                  copyVendorSettings(settingsFile);
              }
              mAtomicFile = new AtomicFile(settingsFile, WM_DISPLAY_COMMIT_TAG);
          }
  
          private static void copyVendorSettings(File target) {
              final File vendorFile = new File(Environment.getVendorDirectory(),
                      VENDOR_DISPLAY_SETTINGS_PATH);
              if (vendorFile.canRead()) {
                  try {
                      FileUtils.copy(vendorFile, target);
                  } catch (IOException e) {
                      Slog.e(TAG, "Failed to copy vendor display_settings.xml");
                  }
              }
          }
  
          @Override
          public InputStream openRead() throws FileNotFoundException {
              return mAtomicFile.openRead();
          }
  
          @Override
          public OutputStream startWrite() throws IOException {
              return mAtomicFile.startWrite();
          }
  
          @Override
          public void finishWrite(OutputStream os, boolean success) {
              if (!(os instanceof FileOutputStream)) {
                  throw new IllegalArgumentException("Unexpected OutputStream as argument: " + os);
              }
              FileOutputStream fos = (FileOutputStream) os;
              if (success) {
                  mAtomicFile.finishWrite(fos);
              } else {
                  mAtomicFile.failWrite(fos);
              }
          }
      }
  }
  