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

Commit 66f9b683 authored by Eric Lin's avatar Eric Lin
Browse files

Store display window settings per user to avoid conflicts.

This change resolves conflicts arising from shared display window
settings between users. The density preference is stored in both
per-user secure settings `display_density_forced` and the shared
display_settings.xml written by DisplayWindowSettingsProvider.
The window manager service reads display density from settings during
display initialization and user switching, showing correct density
before device folding. However, after folding, it update display
content from the DisplayWindowSettingsProvider, leading to incorrect
density values based on the previously active user's settings.

This change store display window settings in user-specific data
directories, allowing each user's window settings to persist
independently. The window manager service has been updated to
notify the DisplayWindowSettingsProvider during user switching.
Additionally, stale settings are now removed upon initialization
to ensure settings accuracy and optimize storage resources.

Fix: 346668297
Flag: com.android.window.flags.per_user_display_window_settings
Test: atest WmTests:DisplayWindowSettingsProviderTests
Change-Id: I7d34939212ec3aa7af698cccdd23f1cfdecb8b7b
parent c5002e47
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -181,3 +181,14 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    namespace: "windowing_sdk"
    name: "per_user_display_window_settings"
    description: "Whether to store display window settings per user to avoid conflicts"
    bug: "346668297"
    is_fixed_read_only: true
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+62 −5
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.wm;

import static android.os.UserHandle.USER_SYSTEM;
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;
@@ -27,6 +28,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.WindowConfiguration;
import android.os.Environment;
import android.util.ArrayMap;
@@ -42,6 +44,7 @@ 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.window.flags.Flags;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -53,6 +56,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.Set;

/**
 * Implementation of {@link SettingsProvider} that reads the base settings provided in a display
@@ -91,11 +95,11 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
    @NonNull
    private ReadableSettings mBaseSettings;
    @NonNull
    private final WritableSettings mOverrideSettings;
    private WritableSettings mOverrideSettings;

    DisplayWindowSettingsProvider() {
        this(new AtomicFileStorage(getVendorSettingsFile()),
                new AtomicFileStorage(getOverrideSettingsFile()));
                new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM)));
    }

    @VisibleForTesting
@@ -133,6 +137,48 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
        mBaseSettings = new ReadableSettings(baseSettingsStorage);
    }

    /**
     * Overrides the storage that should be used to save override settings for a user.
     *
     * @see #DATA_DISPLAY_SETTINGS_FILE_PATH
     */
    void setOverrideSettingsForUser(@UserIdInt int userId) {
        if (!Flags.perUserDisplayWindowSettings()) {
            return;
        }
        final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId);
        setOverrideSettingsStorage(new AtomicFileStorage(settingsFile));
    }

    /**
     * Removes display override settings that are no longer associated with active displays.
     * This is necessary because displays can be dynamically added or removed during
     * the system's lifecycle (e.g., user switch, system server restart).
     *
     * @param root The root window container used to obtain the currently active displays.
     */
    void removeStaleDisplaySettings(@NonNull RootWindowContainer root) {
        if (!Flags.perUserDisplayWindowSettings()) {
            return;
        }
        final Set<String> displayIdentifiers = new ArraySet<>();
        root.forAllDisplays(dc -> {
            final String identifier = mOverrideSettings.getIdentifier(dc.getDisplayInfo());
            displayIdentifiers.add(identifier);
        });
        mOverrideSettings.removeStaleDisplaySettings(displayIdentifiers);
    }

    /**
     * Overrides the storage that should be used to save override settings.
     *
     * @see #setOverrideSettingsForUser(int)
     */
    @VisibleForTesting
    void setOverrideSettingsStorage(@NonNull WritableSettingsStorage overrideSettingsStorage) {
        mOverrideSettings = new WritableSettings(overrideSettingsStorage);
    }

    @Override
    @NonNull
    public SettingsEntry getSettings(@NonNull DisplayInfo info) {
@@ -302,6 +348,12 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
            mVirtualDisplayIdentifiers.remove(identifier);
        }

        void removeStaleDisplaySettings(@NonNull Set<String> currentDisplayIdentifiers) {
            if (mSettings.retainAll(currentDisplayIdentifiers)) {
                writeSettings();
            }
        }

        private void writeSettings() {
            final FileData fileData = new FileData();
            fileData.mIdentifierType = mIdentifierType;
@@ -332,9 +384,14 @@ class DisplayWindowSettingsProvider implements SettingsProvider {
    }

    @NonNull
    private static AtomicFile getOverrideSettingsFile() {
        final File overrideSettingsFile = new File(Environment.getDataDirectory(),
                DATA_DISPLAY_SETTINGS_FILE_PATH);
    private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) {
        final File directory;
        if (userId == USER_SYSTEM || !Flags.perUserDisplayWindowSettings()) {
            directory = Environment.getDataDirectory();
        } else {
            directory = Environment.getDataSystemCeDirectory(userId);
        }
        final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH);
        return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG);
    }

+5 −0
Original line number Diff line number Diff line
@@ -3743,6 +3743,8 @@ public class WindowManagerService extends IWindowManager.Stub
                        null /* trigger */, null /* remote */, null /* disp */);
            }
            mCurrentUserId = newUserId;
            mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId);
            mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
            mPolicy.setCurrentUserLw(newUserId);
            mKeyguardDisableHandler.setCurrentUser(newUserId);

@@ -5479,6 +5481,9 @@ public class WindowManagerService extends IWindowManager.Stub
            // DisplayWindowSettings are applied. In addition, wide-color/hdr/isTouchDevice also
            // affect the Configuration.
            mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);
            // Per-user display settings may leave outdated settings after user switches, especially
            // during reboots starting with the default user without setCurrentUser called.
            mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
        }
    }

+89 −33
Original line number Diff line number Diff line
@@ -24,9 +24,12 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertFalse;

import android.annotation.Nullable;
@@ -55,6 +58,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

/**
 * Tests for the {@link DisplayWindowSettingsProvider} class.
@@ -128,9 +132,8 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        // Update settings with new value, should trigger write to injector.
        DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        SettingsEntry overrideSettings = provider.getOverrideSettings(mPrimaryDisplayInfo);
        overrideSettings.mForcedDensity = 200;
        provider.updateOverrideSettings(mPrimaryDisplayInfo, overrideSettings);
        updateOverrideSettings(provider, mPrimaryDisplayInfo,
                overrideSettings -> overrideSettings.mForcedDensity = 200);
        assertTrue(mOverrideSettingsStorage.wasWriteSuccessful());

        // Verify that display identifier was updated.
@@ -167,7 +170,7 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
    }

    @Test
    public void testReadingDisplaySettingsFromStorage_secondayVendorDisplaySettingsLocation() {
    public void testReadingDisplaySettingsFromStorage_secondaryVendorDisplaySettingsLocation() {
        final String displayIdentifier = mSecondaryDisplay.getDisplayInfo().uniqueId;
        prepareSecondaryDisplaySettings(displayIdentifier);

@@ -216,11 +219,11 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        // Write some settings to storage.
        DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
        updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> {
            overrideSettings.mShouldShowSystemDecors = true;
            overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
            overrideSettings.mDontMoveToTop = true;
        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
        });
        assertTrue(mOverrideSettingsStorage.wasWriteSuccessful());

        // Verify that settings were stored correctly.
@@ -234,6 +237,29 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
                getStoredDisplayAttributeValue(mOverrideSettingsStorage, "dontMoveToTop"));
    }

    @Test
    public void testWritingDisplaySettingsToStorage_secondaryUserDisplaySettingsLocation() {
        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        final DisplayInfo displayInfo = mPrimaryDisplay.getDisplayInfo();
        final TestStorage secondaryUserOverrideSettingsStorage = new TestStorage();
        final SettingsEntry expectedSettings = new SettingsEntry();
        expectedSettings.mForcedDensity = 356;

        // Write some settings to storage from default user.
        updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356);
        assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();

        // Now switch to secondary user override settings and write some settings.
        provider.setOverrideSettingsStorage(secondaryUserOverrideSettingsStorage);
        updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 420);
        assertThat(secondaryUserOverrideSettingsStorage.wasWriteSuccessful()).isTrue();

        // Switch back to primary and assert default user settings remain unchanged.
        provider.setOverrideSettingsStorage(mOverrideSettingsStorage);
        assertThat(provider.getOverrideSettings(displayInfo)).isEqualTo(expectedSettings);
    }

    @Test
    public void testDoNotWriteVirtualDisplaySettingsToStorage() throws Exception {
        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
@@ -242,11 +268,11 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        // No write to storage on virtual display change.
        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        final SettingsEntry virtualSettings = provider.getOverrideSettings(secondaryDisplayInfo);
        updateOverrideSettings(provider, secondaryDisplayInfo, virtualSettings -> {
            virtualSettings.mShouldShowSystemDecors = true;
            virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
            virtualSettings.mDontMoveToTop = true;
        provider.updateOverrideSettings(secondaryDisplayInfo, virtualSettings);
        });
        assertFalse(mOverrideSettingsStorage.wasWriteSuccessful());
    }

@@ -263,10 +289,10 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        // Write some settings to storage.
        DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);
        updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> {
            overrideSettings.mShouldShowSystemDecors = true;
            overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL;
        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
        });
        assertTrue(mOverrideSettingsStorage.wasWriteSuccessful());

        // Verify that settings were stored correctly.
@@ -283,16 +309,16 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        final int initialSize = provider.getOverrideSettingsSize();

        // Size + 1 when query for a new display.
        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);

        updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> {
            // Size + 1 when query for a new display.
            assertEquals(initialSize + 1, provider.getOverrideSettingsSize());

        // When a display is removed, its override Settings is not removed if there is any override.
            // When a display is removed, its override Settings is not removed if there is any
            // override.
            overrideSettings.mShouldShowSystemDecors = true;
        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
        });
        provider.onDisplayRemoved(secondaryDisplayInfo);

        assertEquals(initialSize + 1, provider.getOverrideSettingsSize());
@@ -309,23 +335,53 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase {
        final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
                mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
        final int initialSize = provider.getOverrideSettingsSize();

        // Size + 1 when query for a new display.
        final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo();
        secondaryDisplayInfo.type = TYPE_VIRTUAL;
        final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo);

        updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> {
            // Size + 1 when query for a new display.
            assertEquals(initialSize + 1, provider.getOverrideSettingsSize());

        // When a virtual display is removed, its override Settings is removed even if it has
        // override.
            // When a virtual display is removed, its override Settings is removed
            // even if it has override.
            overrideSettings.mShouldShowSystemDecors = true;
        provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings);
        });
        provider.onDisplayRemoved(secondaryDisplayInfo);

        assertEquals(initialSize, provider.getOverrideSettingsSize());
    }

    @Test
    public void testRemovesStaleDisplaySettings() {
        assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());

        final DisplayWindowSettingsProvider provider =
                new DisplayWindowSettingsProvider(mDefaultVendorSettingsStorage,
                        mOverrideSettingsStorage);
        final DisplayInfo displayInfo = mSecondaryDisplay.getDisplayInfo();
        updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356);
        mRootWindowContainer.removeChild(mSecondaryDisplay);

        provider.removeStaleDisplaySettings(mRootWindowContainer);

        assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();
        assertThat(provider.getOverrideSettingsSize()).isEqualTo(0);
    }

    /**
     * Updates the override settings for a specific display.
     *
     * @param provider the provider to obtain and update the settings from.
     * @param displayInfo the information about the display to be updated.
     * @param modifier a function that modifies the settings for the display.
     */
    private static void updateOverrideSettings(DisplayWindowSettingsProvider provider,
            DisplayInfo displayInfo, Consumer<SettingsEntry> modifier) {
        final SettingsEntry settings = provider.getOverrideSettings(displayInfo);
        modifier.accept(settings);
        provider.updateOverrideSettings(displayInfo, settings);
    }

    /**
     * Prepares display settings and stores in {@link #mOverrideSettingsStorage}. Uses provided
     * display identifier and stores windowingMode=WINDOWING_MODE_PINNED.