Loading core/java/android/hardware/input/input_framework.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -233,3 +233,12 @@ flag { description: "Key Event Activity Detection" bug: "356412905" } flag { name: "enable_backup_and_restore_for_input_gestures" namespace: "input" description: "Adds backup and restore support for custom input gestures" bug: "382184249" is_fixed_read_only: true } services/core/java/com/android/server/backup/InputBackupHelper.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 static com.android.server.input.InputManagerInternal.BACKUP_CATEGORY_INPUT_GESTURES; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.backup.BlobBackupHelper; import android.util.Slog; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; import java.util.HashMap; import java.util.Map; public class InputBackupHelper extends BlobBackupHelper { private static final String TAG = "InputBackupHelper"; // must be < 23 chars // Current version of the blob schema private static final int BLOB_VERSION = 1; // Key under which the payload blob is stored private static final String KEY_INPUT_GESTURES = "input_gestures"; private final @UserIdInt int mUserId; private final @NonNull InputManagerInternal mInputManagerInternal; public InputBackupHelper(int userId) { super(BLOB_VERSION, KEY_INPUT_GESTURES); mUserId = userId; mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); } @Override protected byte[] getBackupPayload(String key) { Map<Integer, byte[]> payloads; try { payloads = mInputManagerInternal.getBackupPayload(mUserId); } catch (Exception exception) { Slog.e(TAG, "Failed to get backup payload for input gestures", exception); return null; } if (KEY_INPUT_GESTURES.equals(key)) { return payloads.getOrDefault(BACKUP_CATEGORY_INPUT_GESTURES, null); } return null; } @Override protected void applyRestoredPayload(String key, byte[] payload) { Map<Integer, byte[]> payloads = new HashMap<>(); if (KEY_INPUT_GESTURES.equals(key)) { payloads.put(BACKUP_CATEGORY_INPUT_GESTURES, payload); } try { mInputManagerInternal.applyBackupPayload(payloads, mUserId); } catch (Exception exception) { Slog.e(TAG, "Failed to apply input backup payload", exception); } } } services/core/java/com/android/server/backup/SystemBackupAgent.java +5 −1 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String COMPANION_HELPER = "companion"; private static final String SYSTEM_GENDER_HELPER = "system_gender"; private static final String DISPLAY_HELPER = "display"; private static final String INPUT_HELPER = "input"; // 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 @@ -112,7 +113,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final Set<String> sEligibleHelpersForNonSystemUser = SetUtils.union(sEligibleHelpersForProfileUser, Sets.newArraySet(ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, SHORTCUT_MANAGER_HELPER)); SHORTCUT_MANAGER_HELPER, INPUT_HELPER)); private int mUserId = UserHandle.USER_SYSTEM; private boolean mIsProfileUser = false; Loading Loading @@ -149,6 +150,9 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER, new SystemGrammaticalGenderBackupHelper(mUserId)); addHelperIfEligibleForUser(DISPLAY_HELPER, new DisplayBackupHelper(mUserId)); if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) { addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId)); } } @Override Loading services/core/java/com/android/server/input/InputDataStore.java +39 −20 Original line number Diff line number Diff line Loading @@ -125,8 +125,20 @@ public final class InputDataStore { } } @VisibleForTesting List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded) /** * Parses the given input stream and returns the list of {@link InputGestureData} objects. * This parsing happens on a best effort basis. If invalid data exists in the given payload * it will be skipped. An example of this would be a keycode that does not exist in the * present version of Android. If the payload is malformed, instead this will throw an * exception and require the caller to handel this appropriately for its situation. * * @param stream stream of the input payload of XML data * @param utf8Encoded whether or not the input data is UTF-8 encoded * @return list of {@link InputGestureData} objects pulled from the payload * @throws XmlPullParserException * @throws IOException */ public List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded) throws XmlPullParserException, IOException { List<InputGestureData> inputGestureDataList = new ArrayList<>(); TypedXmlPullParser parser; Loading @@ -153,6 +165,31 @@ public final class InputDataStore { return inputGestureDataList; } /** * Serializes the given list of {@link InputGestureData} objects to XML in the provided output * stream. * * @param stream output stream to put serialized data. * @param utf8Encoded whether or not to encode the serialized data in UTF-8 format. * @param inputGestureDataList the list of {@link InputGestureData} objects to serialize. */ public void writeInputGestureXml(OutputStream stream, boolean utf8Encoded, List<InputGestureData> inputGestureDataList) throws IOException { final TypedXmlSerializer serializer; if (utf8Encoded) { serializer = Xml.newFastSerializer(); serializer.setOutput(stream, StandardCharsets.UTF_8.name()); } else { serializer = Xml.resolveSerializer(stream); } serializer.startDocument(null, true); serializer.startTag(null, TAG_ROOT); writeInputGestureListToXml(serializer, inputGestureDataList); serializer.endTag(null, TAG_ROOT); serializer.endDocument(); } private InputGestureData readInputGestureFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException, IllegalArgumentException { InputGestureData.Builder builder = new InputGestureData.Builder(); Loading Loading @@ -239,24 +276,6 @@ public final class InputDataStore { return inputGestureDataList; } @VisibleForTesting void writeInputGestureXml(OutputStream stream, boolean utf8Encoded, List<InputGestureData> inputGestureDataList) throws IOException { final TypedXmlSerializer serializer; if (utf8Encoded) { serializer = Xml.newFastSerializer(); serializer.setOutput(stream, StandardCharsets.UTF_8.name()); } else { serializer = Xml.resolveSerializer(stream); } serializer.startDocument(null, true); serializer.startTag(null, TAG_ROOT); writeInputGestureListToXml(serializer, inputGestureDataList); serializer.endTag(null, TAG_ROOT); serializer.endDocument(); } private void writeInputGestureToXml(TypedXmlSerializer serializer, InputGestureData inputGestureData) throws IOException { serializer.startTag(null, TAG_INPUT_GESTURE); Loading services/core/java/com/android/server/input/InputManagerInternal.java +32 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.input; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; Loading @@ -32,7 +33,11 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import com.android.internal.policy.IShortcutService; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.List; import java.util.Map; /** * Input manager local system service interface. Loading @@ -41,6 +46,15 @@ import java.util.List; */ public abstract class InputManagerInternal { // Backup and restore information for custom input gestures. public static final int BACKUP_CATEGORY_INPUT_GESTURES = 0; // Backup and Restore categories for sending map of data back and forth to backup and restore // infrastructure. @IntDef({BACKUP_CATEGORY_INPUT_GESTURES}) public @interface BackupCategory { } /** * Called by the display manager to set information about the displays as needed * by the input system. The input system must copy this information to retain it. Loading Loading @@ -312,4 +326,22 @@ public abstract class InputManagerInternal { * @return true if setting power wakeup was successful. */ public abstract boolean setKernelWakeEnabled(int deviceId, boolean enabled); /** * Retrieves the input gestures backup payload data. * * @param userId the user ID of the backup data. * @return byte array of UTF-8 encoded backup data. */ public abstract Map<Integer, byte[]> getBackupPayload(int userId) throws IOException; /** * Applies the given UTF-8 encoded byte array payload to the given user's input data * on a best effort basis. * * @param payload UTF-8 encoded map of byte arrays of restored data * @param userId the user ID for which to apply the payload data */ public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId) throws XmlPullParserException, IOException; } Loading
core/java/android/hardware/input/input_framework.aconfig +9 −0 Original line number Diff line number Diff line Loading @@ -233,3 +233,12 @@ flag { description: "Key Event Activity Detection" bug: "356412905" } flag { name: "enable_backup_and_restore_for_input_gestures" namespace: "input" description: "Adds backup and restore support for custom input gestures" bug: "382184249" is_fixed_read_only: true }
services/core/java/com/android/server/backup/InputBackupHelper.java 0 → 100644 +82 −0 Original line number Diff line number Diff line /* * Copyright 2024 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 static com.android.server.input.InputManagerInternal.BACKUP_CATEGORY_INPUT_GESTURES; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.backup.BlobBackupHelper; import android.util.Slog; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; import java.util.HashMap; import java.util.Map; public class InputBackupHelper extends BlobBackupHelper { private static final String TAG = "InputBackupHelper"; // must be < 23 chars // Current version of the blob schema private static final int BLOB_VERSION = 1; // Key under which the payload blob is stored private static final String KEY_INPUT_GESTURES = "input_gestures"; private final @UserIdInt int mUserId; private final @NonNull InputManagerInternal mInputManagerInternal; public InputBackupHelper(int userId) { super(BLOB_VERSION, KEY_INPUT_GESTURES); mUserId = userId; mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); } @Override protected byte[] getBackupPayload(String key) { Map<Integer, byte[]> payloads; try { payloads = mInputManagerInternal.getBackupPayload(mUserId); } catch (Exception exception) { Slog.e(TAG, "Failed to get backup payload for input gestures", exception); return null; } if (KEY_INPUT_GESTURES.equals(key)) { return payloads.getOrDefault(BACKUP_CATEGORY_INPUT_GESTURES, null); } return null; } @Override protected void applyRestoredPayload(String key, byte[] payload) { Map<Integer, byte[]> payloads = new HashMap<>(); if (KEY_INPUT_GESTURES.equals(key)) { payloads.put(BACKUP_CATEGORY_INPUT_GESTURES, payload); } try { mInputManagerInternal.applyBackupPayload(payloads, mUserId); } catch (Exception exception) { Slog.e(TAG, "Failed to apply input backup payload", exception); } } }
services/core/java/com/android/server/backup/SystemBackupAgent.java +5 −1 Original line number Diff line number Diff line Loading @@ -68,6 +68,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String COMPANION_HELPER = "companion"; private static final String SYSTEM_GENDER_HELPER = "system_gender"; private static final String DISPLAY_HELPER = "display"; private static final String INPUT_HELPER = "input"; // 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 @@ -112,7 +113,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final Set<String> sEligibleHelpersForNonSystemUser = SetUtils.union(sEligibleHelpersForProfileUser, Sets.newArraySet(ACCOUNT_MANAGER_HELPER, USAGE_STATS_HELPER, PREFERRED_HELPER, SHORTCUT_MANAGER_HELPER)); SHORTCUT_MANAGER_HELPER, INPUT_HELPER)); private int mUserId = UserHandle.USER_SYSTEM; private boolean mIsProfileUser = false; Loading Loading @@ -149,6 +150,9 @@ public class SystemBackupAgent extends BackupAgentHelper { addHelperIfEligibleForUser(SYSTEM_GENDER_HELPER, new SystemGrammaticalGenderBackupHelper(mUserId)); addHelperIfEligibleForUser(DISPLAY_HELPER, new DisplayBackupHelper(mUserId)); if (com.android.hardware.input.Flags.enableBackupAndRestoreForInputGestures()) { addHelperIfEligibleForUser(INPUT_HELPER, new InputBackupHelper(mUserId)); } } @Override Loading
services/core/java/com/android/server/input/InputDataStore.java +39 −20 Original line number Diff line number Diff line Loading @@ -125,8 +125,20 @@ public final class InputDataStore { } } @VisibleForTesting List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded) /** * Parses the given input stream and returns the list of {@link InputGestureData} objects. * This parsing happens on a best effort basis. If invalid data exists in the given payload * it will be skipped. An example of this would be a keycode that does not exist in the * present version of Android. If the payload is malformed, instead this will throw an * exception and require the caller to handel this appropriately for its situation. * * @param stream stream of the input payload of XML data * @param utf8Encoded whether or not the input data is UTF-8 encoded * @return list of {@link InputGestureData} objects pulled from the payload * @throws XmlPullParserException * @throws IOException */ public List<InputGestureData> readInputGesturesXml(InputStream stream, boolean utf8Encoded) throws XmlPullParserException, IOException { List<InputGestureData> inputGestureDataList = new ArrayList<>(); TypedXmlPullParser parser; Loading @@ -153,6 +165,31 @@ public final class InputDataStore { return inputGestureDataList; } /** * Serializes the given list of {@link InputGestureData} objects to XML in the provided output * stream. * * @param stream output stream to put serialized data. * @param utf8Encoded whether or not to encode the serialized data in UTF-8 format. * @param inputGestureDataList the list of {@link InputGestureData} objects to serialize. */ public void writeInputGestureXml(OutputStream stream, boolean utf8Encoded, List<InputGestureData> inputGestureDataList) throws IOException { final TypedXmlSerializer serializer; if (utf8Encoded) { serializer = Xml.newFastSerializer(); serializer.setOutput(stream, StandardCharsets.UTF_8.name()); } else { serializer = Xml.resolveSerializer(stream); } serializer.startDocument(null, true); serializer.startTag(null, TAG_ROOT); writeInputGestureListToXml(serializer, inputGestureDataList); serializer.endTag(null, TAG_ROOT); serializer.endDocument(); } private InputGestureData readInputGestureFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException, IllegalArgumentException { InputGestureData.Builder builder = new InputGestureData.Builder(); Loading Loading @@ -239,24 +276,6 @@ public final class InputDataStore { return inputGestureDataList; } @VisibleForTesting void writeInputGestureXml(OutputStream stream, boolean utf8Encoded, List<InputGestureData> inputGestureDataList) throws IOException { final TypedXmlSerializer serializer; if (utf8Encoded) { serializer = Xml.newFastSerializer(); serializer.setOutput(stream, StandardCharsets.UTF_8.name()); } else { serializer = Xml.resolveSerializer(stream); } serializer.startDocument(null, true); serializer.startTag(null, TAG_ROOT); writeInputGestureListToXml(serializer, inputGestureDataList); serializer.endTag(null, TAG_ROOT); serializer.endDocument(); } private void writeInputGestureToXml(TypedXmlSerializer serializer, InputGestureData inputGestureData) throws IOException { serializer.startTag(null, TAG_INPUT_GESTURE); Loading
services/core/java/com/android/server/input/InputManagerInternal.java +32 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.input; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; Loading @@ -32,7 +33,11 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.InputMethodSubtypeHandle; import com.android.internal.policy.IShortcutService; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.List; import java.util.Map; /** * Input manager local system service interface. Loading @@ -41,6 +46,15 @@ import java.util.List; */ public abstract class InputManagerInternal { // Backup and restore information for custom input gestures. public static final int BACKUP_CATEGORY_INPUT_GESTURES = 0; // Backup and Restore categories for sending map of data back and forth to backup and restore // infrastructure. @IntDef({BACKUP_CATEGORY_INPUT_GESTURES}) public @interface BackupCategory { } /** * Called by the display manager to set information about the displays as needed * by the input system. The input system must copy this information to retain it. Loading Loading @@ -312,4 +326,22 @@ public abstract class InputManagerInternal { * @return true if setting power wakeup was successful. */ public abstract boolean setKernelWakeEnabled(int deviceId, boolean enabled); /** * Retrieves the input gestures backup payload data. * * @param userId the user ID of the backup data. * @return byte array of UTF-8 encoded backup data. */ public abstract Map<Integer, byte[]> getBackupPayload(int userId) throws IOException; /** * Applies the given UTF-8 encoded byte array payload to the given user's input data * on a best effort basis. * * @param payload UTF-8 encoded map of byte arrays of restored data * @param userId the user ID for which to apply the payload data */ public abstract void applyBackupPayload(Map<Integer, byte[]> payload, int userId) throws XmlPullParserException, IOException; }