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

Commit 94f56763 authored by David Padlipsky's avatar David Padlipsky
Browse files

Implement backup and restore of input gesture customizations

Test: atest InputTests:KeyGestureControllerTests
Test: Manually tested backup + restore on device
Bug: 365064144
Bug: 382184249
Flag: com.android.hardware.input.enable_backup_and_restore_for_input_gestures

Change-Id: Iff0358fd2f4cd8efa7adde103befc70017871d7f
parent 31719bf9
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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
}
+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);
        }
    }

}
+5 −1
Original line number Diff line number Diff line
@@ -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
@@ -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;
@@ -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
+39 −20
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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);
+32 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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.
@@ -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