Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7b610860 authored by Ebru Kurnaz's avatar Ebru Kurnaz
Browse files

Extract XML read/write operations in DisplayWindowSettingsProvider into helper class.

This class will later be used by to backup/restore DisplayWindowSettings.

Bug: 346499008
Flag: EXEMPT structural refactor
Test: atest DisplayWindowSettingsProviderTest, DisplayWindowSettingsXmlHelperTests
Change-Id: I69bf19784c391f18b0aeee177f6b4b2958e9055c
parent 906feec8
Loading
Loading
Loading
Loading
+26 −292
Original line number Original line Diff line number Diff line
@@ -18,36 +18,26 @@ package com.android.server.wm;


import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.TYPE_VIRTUAL;
import static android.view.Display.TYPE_VIRTUAL;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;


import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.DisplayWindowSettingsXmlHelper.IDENTIFIER_PORT;
import static com.android.server.wm.DisplayWindowSettingsXmlHelper.DisplayIdentifierType;


import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
import android.os.Environment;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AtomicFile;
import android.util.LruCache;
import android.util.LruCache;
import android.util.Slog;
import android.util.Slog;
import android.util.Xml;
import android.view.DisplayAddress;
import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.DisplayInfo;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider;
import com.android.server.wm.DisplayWindowSettings.SettingsProvider;

import com.android.server.wm.DisplayWindowSettingsXmlHelper.FileData;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;


import java.io.File;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileNotFoundException;
@@ -79,14 +69,6 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
     */
     */
    private static final int MAX_NUMBER_OF_DISPLAY_SETTINGS = 100;
    private static final int MAX_NUMBER_OF_DISPLAY_SETTINGS = 100;


    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 {}

    /** Interface that allows reading the display window settings. */
    /** Interface that allows reading the display window settings. */
    interface ReadableSettingsStorage {
    interface ReadableSettingsStorage {
        InputStream openRead() throws IOException;
        InputStream openRead() throws IOException;
@@ -256,7 +238,16 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
        }
        }


        private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) {
        private void loadSettings(@NonNull ReadableSettingsStorage settingsStorage) {
            FileData fileData = readSettings(settingsStorage);
            InputStream stream;
            FileData fileData;
            try {
                stream = settingsStorage.openRead();
            } catch (IOException e) {
                Slog.i(TAG, "No existing display settings, starting empty");
                return;
            }

            fileData = FileData.readSettings(stream);
            if (fileData != null) {
            if (fileData != null) {
                mIdentifierType = fileData.mIdentifierType;
                mIdentifierType = fileData.mIdentifierType;
                for (final Map.Entry<String, SettingsEntry> entry : fileData.mSettings.entrySet()) {
                for (final Map.Entry<String, SettingsEntry> entry : fileData.mSettings.entrySet()) {
@@ -346,7 +337,18 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
                }
                }
                fileData.mSettings.put(identifier, entry.getValue());
                fileData.mSettings.put(identifier, entry.getValue());
            }
            }
            DisplayWindowSettingsProvider.writeSettings(mSettingsStorage, fileData);
            OutputStream stream = null;
            boolean success = false;
            try {
                stream = mSettingsStorage.startWrite();
                success = DisplayWindowSettingsXmlHelper.writeSettings(stream, fileData, false);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to write display settings: " + e);
            } finally {
                if (stream != null) {
                    mSettingsStorage.finishWrite(stream, success);
                }
            }
        }
        }
    }
    }


@@ -364,7 +366,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
    }
    }


    @NonNull
    @NonNull
    private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
    public static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
        final File directory = (userId == USER_SYSTEM)
        final File directory = (userId == USER_SYSTEM)
                ? Environment.getDataDirectory()
                ? Environment.getDataDirectory()
                : Environment.getDataSystemCeDirectory(userId);
                : Environment.getDataSystemCeDirectory(userId);
@@ -372,274 +374,6 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
        return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
        return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
    }
    }


    @Nullable
    private static FileData readSettings(@NonNull ReadableSettingsStorage storage) {
        InputStream stream;
        try {
            stream = storage.openRead();
        } catch (IOException e) {
            Slog.i(TAG, "No existing display settings, starting empty");
            return null;
        }
        FileData fileData = new FileData();
        boolean success = false;
        try {
            TypedXmlPullParser parser = Xml.resolvePullParser(stream);
            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, fileData);
                } else if (tagName.equals("config")) {
                    readConfig(parser, fileData);
                } 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 {
            try {
                stream.close();
            } catch (IOException ignored) {
            }
        }
        if (!success) {
            fileData.mSettings.clear();
        }
        return fileData;
    }

    private static int getIntAttribute(@NonNull TypedXmlPullParser parser, @NonNull String name,
            int defaultValue) {
        return parser.getAttributeInt(null, name, defaultValue);
    }

    @Nullable
    private static Integer getIntegerAttribute(@NonNull TypedXmlPullParser parser,
            @NonNull String name, @Nullable Integer defaultValue) {
        try {
            return parser.getAttributeInt(null, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    @Nullable
    private static Boolean getBooleanAttribute(@NonNull TypedXmlPullParser parser,
            @NonNull String name, @Nullable Boolean defaultValue) {
        try {
            return parser.getAttributeBoolean(null, name);
        } catch (Exception ignored) {
            return defaultValue;
        }
    }

    private static void readDisplay(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
            throws NumberFormatException, XmlPullParserException, IOException {
        String name = parser.getAttributeValue(null, "name");
        if (name != null) {
            SettingsEntry settingsEntry = new SettingsEntry();
            settingsEntry.mWindowingMode = getIntAttribute(parser, "windowingMode",
                    WindowConfiguration.WINDOWING_MODE_UNDEFINED /* defaultValue */);
            settingsEntry.mUserRotationMode = getIntegerAttribute(parser, "userRotationMode",
                    null /* defaultValue */);
            settingsEntry.mUserRotation = getIntegerAttribute(parser, "userRotation",
                    null /* defaultValue */);
            settingsEntry.mForcedWidth = getIntAttribute(parser, "forcedWidth",
                    0 /* defaultValue */);
            settingsEntry.mForcedHeight = getIntAttribute(parser, "forcedHeight",
                    0 /* defaultValue */);
            settingsEntry.mForcedDensity = getIntAttribute(parser, "forcedDensity",
                    0 /* defaultValue */);
            settingsEntry.mForcedDensityRatio = parser.getAttributeFloat(null, "forcedDensityRatio",
                    0.0f /* defaultValue */);
            settingsEntry.mForcedScalingMode = getIntegerAttribute(parser, "forcedScalingMode",
                    null /* defaultValue */);
            settingsEntry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",
                    REMOVE_CONTENT_MODE_UNDEFINED /* defaultValue */);
            settingsEntry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,
                    "shouldShowWithInsecureKeyguard", null /* defaultValue */);
            settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser,
                    "shouldShowSystemDecors", null /* defaultValue */);
            final Boolean shouldShowIme = getBooleanAttribute(parser, "shouldShowIme",
                    null /* defaultValue */);
            if (shouldShowIme != null) {
                settingsEntry.mImePolicy = shouldShowIme ? DISPLAY_IME_POLICY_LOCAL
                        : DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
            } else {
                settingsEntry.mImePolicy = getIntegerAttribute(parser, "imePolicy",
                        null /* defaultValue */);
            }
            settingsEntry.mFixedToUserRotation = getIntegerAttribute(parser, "fixedToUserRotation",
                    null /* defaultValue */);
            settingsEntry.mIgnoreOrientationRequest = getBooleanAttribute(parser,
                    "ignoreOrientationRequest", null /* defaultValue */);
            settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser,
                    "ignoreDisplayCutout", null /* defaultValue */);
            settingsEntry.mDontMoveToTop = getBooleanAttribute(parser,
                    "dontMoveToTop", null /* defaultValue */);
            settingsEntry.mIsHomeSupported = getBooleanAttribute(parser,
                    "isHomeSupported", null /* defaultValue */);
            fileData.mSettings.put(name, settingsEntry);
        }
        XmlUtils.skipCurrentTag(parser);
    }

    private static void readConfig(@NonNull TypedXmlPullParser parser, @NonNull FileData fileData)
            throws NumberFormatException,
            XmlPullParserException, IOException {
        fileData.mIdentifierType = getIntAttribute(parser, "identifier",
                IDENTIFIER_UNIQUE_ID);
        XmlUtils.skipCurrentTag(parser);
    }

    private static void writeSettings(@NonNull WritableSettingsStorage storage,
            @NonNull FileData data) {
        OutputStream stream;
        try {
            stream = storage.startWrite();
        } catch (IOException e) {
            Slog.w(TAG, "Failed to write display settings: " + e);
            return;
        }

        boolean success = false;
        try {
            TypedXmlSerializer out = Xml.resolveSerializer(stream);
            out.startDocument(null, true);

            out.startTag(null, "display-settings");

            out.startTag(null, "config");
            out.attributeInt(null, "identifier", data.mIdentifierType);
            out.endTag(null, "config");

            for (Map.Entry<String, SettingsEntry> entry
                    : data.mSettings.entrySet()) {
                String displayIdentifier = entry.getKey();
                SettingsEntry settingsEntry = entry.getValue();
                if (settingsEntry.isEmpty()) {
                    continue;
                }

                out.startTag(null, "display");
                out.attribute(null, "name", displayIdentifier);
                if (settingsEntry.mWindowingMode != WindowConfiguration.WINDOWING_MODE_UNDEFINED) {
                    out.attributeInt(null, "windowingMode", settingsEntry.mWindowingMode);
                }
                if (settingsEntry.mUserRotationMode != null) {
                    out.attributeInt(null, "userRotationMode",
                            settingsEntry.mUserRotationMode);
                }
                if (settingsEntry.mUserRotation != null) {
                    out.attributeInt(null, "userRotation",
                            settingsEntry.mUserRotation);
                }
                if (settingsEntry.mForcedWidth != 0 && settingsEntry.mForcedHeight != 0) {
                    out.attributeInt(null, "forcedWidth", settingsEntry.mForcedWidth);
                    out.attributeInt(null, "forcedHeight", settingsEntry.mForcedHeight);
                }
                if (settingsEntry.mForcedDensity != 0) {
                    out.attributeInt(null, "forcedDensity", settingsEntry.mForcedDensity);
                }
                if (settingsEntry.mForcedDensityRatio != 0.0f) {
                    out.attributeFloat(null, "forcedDensityRatio",
                            settingsEntry.mForcedDensityRatio);
                }
                if (settingsEntry.mForcedScalingMode != null) {
                    out.attributeInt(null, "forcedScalingMode",
                            settingsEntry.mForcedScalingMode);
                }
                if (settingsEntry.mRemoveContentMode != REMOVE_CONTENT_MODE_UNDEFINED) {
                    out.attributeInt(null, "removeContentMode", settingsEntry.mRemoveContentMode);
                }
                if (settingsEntry.mShouldShowWithInsecureKeyguard != null) {
                    out.attributeBoolean(null, "shouldShowWithInsecureKeyguard",
                            settingsEntry.mShouldShowWithInsecureKeyguard);
                }
                if (settingsEntry.mShouldShowSystemDecors != null) {
                    out.attributeBoolean(null, "shouldShowSystemDecors",
                            settingsEntry.mShouldShowSystemDecors);
                }
                if (settingsEntry.mImePolicy != null) {
                    out.attributeInt(null, "imePolicy", settingsEntry.mImePolicy);
                }
                if (settingsEntry.mFixedToUserRotation != null) {
                    out.attributeInt(null, "fixedToUserRotation",
                            settingsEntry.mFixedToUserRotation);
                }
                if (settingsEntry.mIgnoreOrientationRequest != null) {
                    out.attributeBoolean(null, "ignoreOrientationRequest",
                            settingsEntry.mIgnoreOrientationRequest);
                }
                if (settingsEntry.mIgnoreDisplayCutout != null) {
                    out.attributeBoolean(null, "ignoreDisplayCutout",
                            settingsEntry.mIgnoreDisplayCutout);
                }
                if (settingsEntry.mDontMoveToTop != null) {
                    out.attributeBoolean(null, "dontMoveToTop",
                            settingsEntry.mDontMoveToTop);
                }
                if (settingsEntry.mIsHomeSupported != null) {
                    out.attributeBoolean(null, "isHomeSupported",
                            settingsEntry.mIsHomeSupported);
                }
                out.endTag(null, "display");
            }

            out.endTag(null, "display-settings");
            out.endDocument();
            success = true;
        } catch (IOException e) {
            Slog.w(TAG, "Failed to write display window settings.", e);
        } finally {
            storage.finishWrite(stream, success);
        }
    }

    private static final class FileData {
        int mIdentifierType;
        @NonNull
        final Map<String, SettingsEntry> mSettings = new ArrayMap<>();

        @Override
        public String toString() {
            return "FileData{"
                    + "mIdentifierType=" + mIdentifierType
                    + ", mSettings=" + mSettings
                    + '}';
        }
    }

    private static final class AtomicFileStorage implements WritableSettingsStorage {
    private static final class AtomicFileStorage implements WritableSettingsStorage {
        @NonNull
        @NonNull
        private final AtomicFile mAtomicFile;
        private final AtomicFile mAtomicFile;
+332 −0

File added.

Preview size limit exceeded, changes collapsed.

+163 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.google.common.truth.Truth.assertThat;

import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;

import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntry;
import com.android.server.wm.DisplayWindowSettingsXmlHelper.FileData;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * Unit tests for the {@link DisplayWindowSettingsXmlHelper} class.
 */
@SmallTest
@Presubmit
@WindowTestsBase.UseTestDisplay
@RunWith(WindowTestRunner.class)
public class DisplayWindowSettingsXmlHelperTests {

    private static final String DISPLAY_NAME = "test_id";

    @Test
    public void readSettings_withFullXml_createsCorrectFileData() {
        String xml = createFullXmlString();

        FileData actualData = DisplayWindowSettingsXmlHelper.FileData.readSettings(
                createInputStream(xml));

        FileData expectedData = createFullFileData();
        assertThat(actualData).isEqualTo(expectedData);
    }

    @Test
    public void readSettings_withMalformedXml_returnsEmptyFileData() {
        String xml = createFaultyXmlString();

        FileData result = DisplayWindowSettingsXmlHelper.FileData.readSettings(
                createInputStream(xml));

        assertThat(result.mSettings).isEmpty();
    }

    @Test
    public void writeSettings_forBackupFalse_includesAllAttributes() {
        FileData data = createFullFileData();
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        DisplayWindowSettingsXmlHelper.writeSettings(os, data, false);
        InputStream is = new ByteArrayInputStream(os.toByteArray());

        FileData roundtripData = DisplayWindowSettingsXmlHelper.FileData.readSettings(is);
        assertThat(roundtripData).isEqualTo(data);
    }

    @Test
    public void writeSettings_forBackupTrue_excludesDeviceSpecificAttributes() {
        FileData data = createFullFileData();
        ByteArrayOutputStream os = new ByteArrayOutputStream();

        DisplayWindowSettingsXmlHelper.writeSettings(os, data, true);
        InputStream is = new ByteArrayInputStream(os.toByteArray());

        FileData roundtripData = DisplayWindowSettingsXmlHelper.FileData.readSettings(is);
        assertThat(roundtripData).isNotEqualTo(data);
    }

    /**
     * Creates a fully populated FileData object.
     */
    private FileData createFullFileData() {
        FileData data = new FileData();
        data.mIdentifierType = 1;

        SettingsEntry entry = new SettingsEntry();
        entry.mWindowingMode = 0;
        entry.mUserRotationMode = 0;
        entry.mUserRotation = 0;
        entry.mForcedWidth = 1080;
        entry.mForcedHeight = 1920;
        entry.mForcedDensity = 480;
        entry.mForcedDensityRatio = 1.2f;
        entry.mForcedScalingMode = 0;
        entry.mRemoveContentMode = 0;
        entry.mShouldShowWithInsecureKeyguard = true;
        entry.mShouldShowSystemDecors = false;
        entry.mImePolicy = 2;
        entry.mFixedToUserRotation = 2;
        entry.mIgnoreOrientationRequest = true;
        entry.mIgnoreDisplayCutout = false;
        entry.mDontMoveToTop = true;
        entry.mIsHomeSupported = false;

        data.mSettings.put(DISPLAY_NAME, entry);
        return data;
    }

    /**
     * Creates a XML string that corresponds to the data from createFullFileData().
     */
    private static String createFullXmlString() {
        return "<?xml version='1.0' encoding='utf-8' ?>"
                + "<display-settings>"
                + "  <config identifier=\"1\" />"
                + "  <display name=\"" + DISPLAY_NAME + "\""
                + "    windowingMode=\"0\""
                + "    userRotationMode=\"0\""
                + "    userRotation=\"0\""
                + "    forcedWidth=\"1080\""
                + "    forcedHeight=\"1920\""
                + "    forcedDensity=\"480\""
                + "    forcedDensityRatio=\"1.2\""
                + "    forcedScalingMode=\"0\""
                + "    removeContentMode=\"0\""
                + "    shouldShowWithInsecureKeyguard=\"true\""
                + "    shouldShowSystemDecors=\"false\""
                + "    imePolicy=\"2\""
                + "    fixedToUserRotation=\"2\""
                + "    ignoreOrientationRequest=\"true\""
                + "    ignoreDisplayCutout=\"false\""
                + "    dontMoveToTop=\"true\""
                + "    isHomeSupported=\"false\" />"
                + "</display-settings>";
    }

    private static String createFaultyXmlString() {
        return "<?xml version='1.0' encoding='utf-8' ?>"
                + "<display-settings>"
                + "  <config identifier=\"1\" />"
                + "  <display name=\"" + DISPLAY_NAME + "\"";
    }

    /**
     * Creates an {@link InputStream} from a given String.
     */
    private static InputStream createInputStream(String xml) {
        return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
    }
}