Loading core/java/android/window/DesktopExperienceFlags.java +3 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,9 @@ public enum DesktopExperienceFlags { Flags.FLAG_ENABLE_APP_TO_WEB_EDUCATION_ANIMATION), ENABLE_AUTO_RESTART_ON_DISPLAY_MOVE(Flags::enableAutoRestartOnDisplayMove, false, Flags.FLAG_ENABLE_AUTO_RESTART_ON_DISPLAY_MOVE), ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS( Flags::enableBackupAndRestoreDisplayWindowSettings, false, Flags.FLAG_ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS), ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX( Flags::enableBlockNonDesktopDisplayWindowDragBugfix, false, Flags.FLAG_ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX), Loading services/core/java/com/android/server/backup/DisplayWindowSettingsBackupHelper.java 0 → 100644 +74 −0 Original line number 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.backup; import android.app.backup.BlobBackupHelper; import android.util.Slog; import com.android.server.LocalServices; import com.android.server.display.utils.DebugUtils; import com.android.server.wm.WindowManagerInternal; public class DisplayWindowSettingsBackupHelper extends BlobBackupHelper { private static final String TAG = "DWSBackupHelper"; // current schema of the backup state blob private static final int BLOB_VERSION = 1; // key under which the data blob is committed to back up private static final String KEY_DISPLAY = "display_window"; // To enable these logs, run: // adb shell setprop persist.log.tag.DWSBackupHelper DEBUG // adb reboot private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private final int mUserId; public DisplayWindowSettingsBackupHelper(int userId) { super(BLOB_VERSION, KEY_DISPLAY); mUserId = userId; } @Override protected byte[] getBackupPayload(String key) { Slog.i(TAG, "getBackupPayload for " + key + " user " + mUserId); if (!KEY_DISPLAY.equals(key)) { return null; } WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); if (DEBUG) { Slog.d(TAG, "Handling backup of " + key); } return wmInternal.backupDisplayWindowSettings(mUserId); } @Override protected void applyRestoredPayload(String key, byte[] payload) { Slog.i(TAG, "applyRestoredPayload for " + key + " user " + mUserId); if (!KEY_DISPLAY.equals(key)) { return; } WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); if (DEBUG) { Slog.d(TAG, "Handling restore of " + key); } wmInternal.restoreDisplayWindowSettings(mUserId, payload); } } services/core/java/com/android/server/backup/SystemBackupAgent.java +8 −2 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.window.DesktopExperienceFlags; import com.android.server.display.DisplayBackupHelper; import com.android.server.notification.NotificationBackupHelper; Loading Loading @@ -70,6 +71,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String DISPLAY_HELPER = "display"; private static final String INPUT_HELPER = "input"; private static final String WEAR_BACKUP_HELPER = "wear"; private static final String DISPLAY_WINDOW_HELPER = "display_window"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are Loading Loading @@ -108,7 +110,8 @@ public class SystemBackupAgent extends BackupAgentHelper { COMPANION_HELPER, APP_GENDER_HELPER, SYSTEM_GENDER_HELPER, DISPLAY_HELPER); DISPLAY_HELPER, DISPLAY_WINDOW_HELPER); /** Helpers that are enabled for full, non-system users. */ private static final Set<String> sEligibleHelpersForNonSystemUser = Loading Loading @@ -154,7 +157,10 @@ public class SystemBackupAgent extends BackupAgentHelper { if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) { addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId)); } if (DesktopExperienceFlags.ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS.isTrue()) { addHelperIfEligibleForUser(DISPLAY_WINDOW_HELPER, new DisplayWindowSettingsBackupHelper(mUserId)); } // Add Wear helper only if the device is a watch if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { addHelperIfEligibleForUser(WEAR_BACKUP_HELPER, new WearBackupHelper()); Loading services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java +15 −6 Original line number Diff line number Diff line Loading @@ -19,14 +19,16 @@ package com.android.server.wm; import static android.os.UserHandle.USER_SYSTEM; import static android.view.Display.TYPE_VIRTUAL; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.DisplayIdentifierType; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.IDENTIFIER_PORT; 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.DisplayWindowSettingsXmlHelper.IDENTIFIER_PORT; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.DisplayIdentifierType; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.content.Context; import android.os.Environment; import android.util.ArraySet; import android.util.AtomicFile; Loading Loading @@ -84,17 +86,22 @@ class DisplayWindowSettingsProvider implements SettingsProvider { private ReadableSettings mBaseSettings; @NonNull private WritableSettings mOverrideSettings; @NonNull private BackupManager mBackupManager; DisplayWindowSettingsProvider() { DisplayWindowSettingsProvider(@NonNull Context context) { this(new AtomicFileStorage(getVendorSettingsFile()), new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM))); new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM)), new BackupManager(context)); } @VisibleForTesting DisplayWindowSettingsProvider(@NonNull ReadableSettingsStorage baseSettingsStorage, @NonNull WritableSettingsStorage overrideSettingsStorage) { @NonNull WritableSettingsStorage overrideSettingsStorage, @NonNull BackupManager backupManager) { mBaseSettings = new ReadableSettings(baseSettingsStorage); mOverrideSettings = new WritableSettings(overrideSettingsStorage); mBackupManager = backupManager; } /** Loading Loading @@ -169,6 +176,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { public void updateOverrideSettings(@NonNull DisplayInfo info, @NonNull SettingsEntry overrides) { mOverrideSettings.updateSettingsEntry(info, overrides); mBackupManager.dataChanged(); } @Override Loading @@ -179,6 +187,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { @Override public void clearDisplaySettings(@NonNull DisplayInfo info) { mOverrideSettings.clearDisplaySettings(info); mBackupManager.dataChanged(); } @VisibleForTesting Loading Loading @@ -366,7 +375,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { } @NonNull public static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { final File directory = (userId == USER_SYSTEM) ? Environment.getDataDirectory() : Environment.getDataSystemCeDirectory(userId); Loading services/core/java/com/android/server/wm/DisplayWindowSettingsXmlHelper.java +20 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntr import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; Loading Loading @@ -162,6 +163,25 @@ class DisplayWindowSettingsXmlHelper { return success; } /** * Reads display window settings from an {@link InputStream}, filters out device-specific * attributes that should not be backed up, and returns the result as a byte array. * <p> * This method is specifically designed for backup operations. It parses the full settings * file and then re-serializes it, omitting attributes like forced width, height, and * density, which are not suitable for restoration on a different device. * * @param stream The input stream to read the settings from. This stream is closed by the * method upon completion. * @return A byte array containing the filtered XML data, ready for backup. */ public static byte[] readAndFilterSettings(InputStream stream) { FileData data = FileData.readSettings(stream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); writeSettings(byteArrayOutputStream, data, /* forBackup= */ true); return byteArrayOutputStream.toByteArray(); } static final class FileData { int mIdentifierType; @NonNull Loading Loading
core/java/android/window/DesktopExperienceFlags.java +3 −0 Original line number Diff line number Diff line Loading @@ -76,6 +76,9 @@ public enum DesktopExperienceFlags { Flags.FLAG_ENABLE_APP_TO_WEB_EDUCATION_ANIMATION), ENABLE_AUTO_RESTART_ON_DISPLAY_MOVE(Flags::enableAutoRestartOnDisplayMove, false, Flags.FLAG_ENABLE_AUTO_RESTART_ON_DISPLAY_MOVE), ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS( Flags::enableBackupAndRestoreDisplayWindowSettings, false, Flags.FLAG_ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS), ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX( Flags::enableBlockNonDesktopDisplayWindowDragBugfix, false, Flags.FLAG_ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX), Loading
services/core/java/com/android/server/backup/DisplayWindowSettingsBackupHelper.java 0 → 100644 +74 −0 Original line number 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.backup; import android.app.backup.BlobBackupHelper; import android.util.Slog; import com.android.server.LocalServices; import com.android.server.display.utils.DebugUtils; import com.android.server.wm.WindowManagerInternal; public class DisplayWindowSettingsBackupHelper extends BlobBackupHelper { private static final String TAG = "DWSBackupHelper"; // current schema of the backup state blob private static final int BLOB_VERSION = 1; // key under which the data blob is committed to back up private static final String KEY_DISPLAY = "display_window"; // To enable these logs, run: // adb shell setprop persist.log.tag.DWSBackupHelper DEBUG // adb reboot private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); private final int mUserId; public DisplayWindowSettingsBackupHelper(int userId) { super(BLOB_VERSION, KEY_DISPLAY); mUserId = userId; } @Override protected byte[] getBackupPayload(String key) { Slog.i(TAG, "getBackupPayload for " + key + " user " + mUserId); if (!KEY_DISPLAY.equals(key)) { return null; } WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); if (DEBUG) { Slog.d(TAG, "Handling backup of " + key); } return wmInternal.backupDisplayWindowSettings(mUserId); } @Override protected void applyRestoredPayload(String key, byte[] payload) { Slog.i(TAG, "applyRestoredPayload for " + key + " user " + mUserId); if (!KEY_DISPLAY.equals(key)) { return; } WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); if (DEBUG) { Slog.d(TAG, "Handling restore of " + key); } wmInternal.restoreDisplayWindowSettings(mUserId, payload); } }
services/core/java/com/android/server/backup/SystemBackupAgent.java +8 −2 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.window.DesktopExperienceFlags; import com.android.server.display.DisplayBackupHelper; import com.android.server.notification.NotificationBackupHelper; Loading Loading @@ -70,6 +71,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String DISPLAY_HELPER = "display"; private static final String INPUT_HELPER = "input"; private static final String WEAR_BACKUP_HELPER = "wear"; private static final String DISPLAY_WINDOW_HELPER = "display_window"; // These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME // are also used in the full-backup file format, so must not change unless steps are Loading Loading @@ -108,7 +110,8 @@ public class SystemBackupAgent extends BackupAgentHelper { COMPANION_HELPER, APP_GENDER_HELPER, SYSTEM_GENDER_HELPER, DISPLAY_HELPER); DISPLAY_HELPER, DISPLAY_WINDOW_HELPER); /** Helpers that are enabled for full, non-system users. */ private static final Set<String> sEligibleHelpersForNonSystemUser = Loading Loading @@ -154,7 +157,10 @@ public class SystemBackupAgent extends BackupAgentHelper { if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) { addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId)); } if (DesktopExperienceFlags.ENABLE_BACKUP_AND_RESTORE_DISPLAY_WINDOW_SETTINGS.isTrue()) { addHelperIfEligibleForUser(DISPLAY_WINDOW_HELPER, new DisplayWindowSettingsBackupHelper(mUserId)); } // Add Wear helper only if the device is a watch if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { addHelperIfEligibleForUser(WEAR_BACKUP_HELPER, new WearBackupHelper()); Loading
services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java +15 −6 Original line number Diff line number Diff line Loading @@ -19,14 +19,16 @@ package com.android.server.wm; import static android.os.UserHandle.USER_SYSTEM; import static android.view.Display.TYPE_VIRTUAL; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.DisplayIdentifierType; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.IDENTIFIER_PORT; 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.DisplayWindowSettingsXmlHelper.IDENTIFIER_PORT; import static com.android.server.wm.DisplayWindowSettingsXmlHelper.DisplayIdentifierType; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.content.Context; import android.os.Environment; import android.util.ArraySet; import android.util.AtomicFile; Loading Loading @@ -84,17 +86,22 @@ class DisplayWindowSettingsProvider implements SettingsProvider { private ReadableSettings mBaseSettings; @NonNull private WritableSettings mOverrideSettings; @NonNull private BackupManager mBackupManager; DisplayWindowSettingsProvider() { DisplayWindowSettingsProvider(@NonNull Context context) { this(new AtomicFileStorage(getVendorSettingsFile()), new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM))); new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM)), new BackupManager(context)); } @VisibleForTesting DisplayWindowSettingsProvider(@NonNull ReadableSettingsStorage baseSettingsStorage, @NonNull WritableSettingsStorage overrideSettingsStorage) { @NonNull WritableSettingsStorage overrideSettingsStorage, @NonNull BackupManager backupManager) { mBaseSettings = new ReadableSettings(baseSettingsStorage); mOverrideSettings = new WritableSettings(overrideSettingsStorage); mBackupManager = backupManager; } /** Loading Loading @@ -169,6 +176,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { public void updateOverrideSettings(@NonNull DisplayInfo info, @NonNull SettingsEntry overrides) { mOverrideSettings.updateSettingsEntry(info, overrides); mBackupManager.dataChanged(); } @Override Loading @@ -179,6 +187,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { @Override public void clearDisplaySettings(@NonNull DisplayInfo info) { mOverrideSettings.clearDisplaySettings(info); mBackupManager.dataChanged(); } @VisibleForTesting Loading Loading @@ -366,7 +375,7 @@ class DisplayWindowSettingsProvider implements SettingsProvider { } @NonNull public static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { final File directory = (userId == USER_SYSTEM) ? Environment.getDataDirectory() : Environment.getDataSystemCeDirectory(userId); Loading
services/core/java/com/android/server/wm/DisplayWindowSettingsXmlHelper.java +20 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ import com.android.server.wm.DisplayWindowSettings.SettingsProvider.SettingsEntr import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; Loading Loading @@ -162,6 +163,25 @@ class DisplayWindowSettingsXmlHelper { return success; } /** * Reads display window settings from an {@link InputStream}, filters out device-specific * attributes that should not be backed up, and returns the result as a byte array. * <p> * This method is specifically designed for backup operations. It parses the full settings * file and then re-serializes it, omitting attributes like forced width, height, and * density, which are not suitable for restoration on a different device. * * @param stream The input stream to read the settings from. This stream is closed by the * method upon completion. * @return A byte array containing the filtered XML data, ready for backup. */ public static byte[] readAndFilterSettings(InputStream stream) { FileData data = FileData.readSettings(stream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); writeSettings(byteArrayOutputStream, data, /* forBackup= */ true); return byteArrayOutputStream.toByteArray(); } static final class FileData { int mIdentifierType; @NonNull Loading