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

Commit a438ce17 authored by Hiroki Sato's avatar Hiroki Sato Committed by Android Build Coastguard Worker
Browse files

Introduce InputMethodSubtypeSafeList

IMM#getEnabledInputMethodSubtypeList() can return a large list of
subtypes, which may cause a TransactionTooLargeException.

This patch introduces InputMethodSubtypeSafeList to wrap the list as a
byte array, avoiding the exception. This mirrors the existing
InputMethodInfoSafeList pattern introduced in [1].

Additionally, this change extracts the common marshalling logic from
InputMethodInfoSafeList into a new AbstractSafeList and refactors both
SafeList classes to extend it.

[1] I0a7667070fcdf17d34b248a5988c38064588718a

Bug: 449416164
Bug: 449181366
Bug: 449393786
Bug: 449227003
Test: CtsInputMethodTestCases:{InputMethodRegistrationTest,InputMethodInfoTest}
Test: InputMethodCoreTests
Flag: EXEMPT BUGFIX
(cherry picked from commit 1d68a1099be2b99e8410dad01822851287994682)
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:e99cb1a4f240988380e43592d845c64f78e1a6d7
Merged-In: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc
Change-Id: Ied64a9f018fd3e79cfc51ccd82d361b43e5f29dc
parent 2bca2265
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodInfoSafeList;
import com.android.internal.inputmethod.InputMethodSubtypeSafeList;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
@@ -297,8 +298,9 @@ final class IInputMethodManagerGlobalInvoker {
            return new ArrayList<>();
        }
        try {
            return service.getEnabledInputMethodSubtypeList(imiId,
                    allowsImplicitlyEnabledSubtypes, userId);
            return InputMethodSubtypeSafeList.extractFrom(
                    service.getEnabledInputMethodSubtypeList(imiId,
                            allowsImplicitlyEnabledSubtypes, userId));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

/**
 * An abstract base class for creating a {@link Parcelable} container that can hold an arbitrary
 * number of {@link Parcelable} objects without worrying about
 * {@link android.os.TransactionTooLargeException}.
 *
 * @see Parcel#readBlob()
 * @see Parcel#writeBlob(byte[])
 *
 * @param <T> The type of the {@link Parcelable} objects.
 */
public abstract class AbstractSafeList<T extends Parcelable> implements Parcelable {
    @Nullable
    private byte[] mBuffer;

    protected AbstractSafeList(@Nullable List<T> list) {
        if (list != null && !list.isEmpty()) {
            mBuffer = marshall(list);
        }
    }

    protected AbstractSafeList(@Nullable byte[] buffer) {
        mBuffer = buffer;
    }

    /**
     * Extracts the list of {@link Parcelable} objects from a {@link AbstractSafeList}, and
     * clears the internal buffer of the list.
     *
     * @param from The {@link AbstractSafeList} to extract from.
     * @param creator The {@link Parcelable.Creator} for the {@link Parcelable} objects.
     * @param <T> The type of the {@link Parcelable} objects.
     * @return The list of {@link Parcelable} objects.
     */
    @NonNull
    protected static <T extends Parcelable> List<T> extractFrom(
            @Nullable AbstractSafeList<T> from, @NonNull Parcelable.Creator<T> creator) {
        if (from == null) {
            return new ArrayList<>();
        }
        final byte[] buf = from.mBuffer;
        from.mBuffer = null;
        if (buf != null) {
            final List<T> list = unmarshall(buf, creator);
            if (list != null) {
                return list;
            }
        }
        return new ArrayList<>();
    }

    @Override
    public int describeContents() {
        // As long as the parcelled classes return 0, we can also return 0 here.
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeBlob(mBuffer);
    }

    /**
     * Marshalls a list of {@link Parcelable} objects into a byte array.
     */
    @Nullable
    @VisibleForTesting
    public static <T extends Parcelable> byte[] marshall(@NonNull List<T> list) {
        Parcel parcel = null;
        try {
            parcel = Parcel.obtain();
            parcel.writeTypedList(list);
            return parcel.marshall();
        } finally {
            if (parcel != null) {
                parcel.recycle();
            }
        }
    }

    /**
     * Unmarshalls a byte array into a list of {@link Parcelable} objects.
     */
    @Nullable
    @VisibleForTesting
    public static <T extends Parcelable> List<T> unmarshall(
            @NonNull byte[] data, @NonNull Parcelable.Creator<T> creator) {
        Parcel parcel = null;
        try {
            parcel = Parcel.obtain();
            parcel.unmarshall(data, 0, data.length);
            parcel.setDataPosition(0);
            return parcel.createTypedArrayList(creator);
        } finally {
            if (parcel != null) {
                parcel.recycle();
            }
        }
    }
}
+16 −89
Original line number Diff line number Diff line
@@ -19,24 +19,24 @@ package com.android.internal.inputmethod;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.inputmethod.InputMethodInfo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * A {@link Parcelable} container that can holds an arbitrary number of {@link InputMethodInfo}
 * without worrying about {@link android.os.TransactionTooLargeException} when passing across
 * process boundary.
 *
 * @see Parcel#readBlob()
 * @see Parcel#writeBlob(byte[])
 * A {@link android.os.Parcelable} container that can hold an arbitrary number of
 * {@link InputMethodInfo} without worrying about
 * {@link android.os.TransactionTooLargeException} when passing across process boundary.
 */
public final class InputMethodInfoSafeList implements Parcelable {
    @Nullable
    private byte[] mBuffer;
public final class InputMethodInfoSafeList extends AbstractSafeList<InputMethodInfo> {

    private InputMethodInfoSafeList(@Nullable byte[] buffer) {
        super(buffer);
    }

    private InputMethodInfoSafeList(@Nullable List<InputMethodInfo> list) {
        super(list);
    }

    /**
     * Instantiates a list of {@link InputMethodInfo} from the given {@link InputMethodInfoSafeList}
@@ -53,81 +53,20 @@ public final class InputMethodInfoSafeList implements Parcelable {
     */
    @NonNull
    public static List<InputMethodInfo> extractFrom(@Nullable InputMethodInfoSafeList from) {
        final byte[] buf = from.mBuffer;
        from.mBuffer = null;
        if (buf != null) {
            final InputMethodInfo[] array = unmarshall(buf);
            if (array != null) {
                return new ArrayList<>(Arrays.asList(array));
            }
        }
        return new ArrayList<>();
    }

    @NonNull
    private static InputMethodInfo[] toArray(@Nullable List<InputMethodInfo> original) {
        if (original == null) {
            return new InputMethodInfo[0];
        }
        return original.toArray(new InputMethodInfo[0]);
    }

    @Nullable
    private static byte[] marshall(@NonNull InputMethodInfo[] array) {
        Parcel parcel = null;
        try {
            parcel = Parcel.obtain();
            parcel.writeTypedArray(array, 0);
            return parcel.marshall();
        } finally {
            if (parcel != null) {
                parcel.recycle();
            }
        }
    }

    @Nullable
    private static InputMethodInfo[] unmarshall(byte[] data) {
        Parcel parcel = null;
        try {
            parcel = Parcel.obtain();
            parcel.unmarshall(data, 0, data.length);
            parcel.setDataPosition(0);
            return parcel.createTypedArray(InputMethodInfo.CREATOR);
        } finally {
            if (parcel != null) {
                parcel.recycle();
            }
        }
    }

    private InputMethodInfoSafeList(@Nullable byte[] blob) {
        mBuffer = blob;
        return AbstractSafeList.extractFrom(from, InputMethodInfo.CREATOR);
    }

    /**
     * Instantiates {@link InputMethodInfoSafeList} from the given list of {@link InputMethodInfo}.
     *
     * @param list list of {@link InputMethodInfo} from which {@link InputMethodInfoSafeList} will
     *             be created
     *             be created. Giving {@code null} will result in an empty
     *             {@link InputMethodInfoSafeList}.
     * @return {@link InputMethodInfoSafeList} that stores the given list of {@link InputMethodInfo}
     */
    @NonNull
    public static InputMethodInfoSafeList create(@Nullable List<InputMethodInfo> list) {
        if (list == null || list.isEmpty()) {
            return empty();
        }
        return new InputMethodInfoSafeList(marshall(toArray(list)));
    }

    /**
     * Creates an empty {@link InputMethodInfoSafeList}.
     *
     * @return {@link InputMethodInfoSafeList} that is empty
     */
    @NonNull
    public static InputMethodInfoSafeList empty() {
        return new InputMethodInfoSafeList(null);
        return new InputMethodInfoSafeList(list);
    }

    public static final Creator<InputMethodInfoSafeList> CREATOR = new Creator<>() {
@@ -141,16 +80,4 @@ public final class InputMethodInfoSafeList implements Parcelable {
            return new InputMethodInfoSafeList[size];
        }
    };

    @Override
    public int describeContents() {
        // As long as InputMethodInfo#describeContents() is guaranteed to return 0, we can always
        // return 0 here.
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeBlob(mBuffer);
    }
}
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.inputmethod;

parcelable InputMethodSubtypeSafeList;
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.internal.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.view.inputmethod.InputMethodSubtype;

import java.util.List;

/**
 * A {@link android.os.Parcelable} container that can hold an arbitrary number of
 * {@link InputMethodSubtype} without worrying about
 * {@link android.os.TransactionTooLargeException} when passing across process boundary.
 */
public final class InputMethodSubtypeSafeList extends AbstractSafeList<InputMethodSubtype> {

    private InputMethodSubtypeSafeList(@Nullable byte[] buffer) {
        super(buffer);
    }

    private InputMethodSubtypeSafeList(@Nullable List<InputMethodSubtype> list) {
        super(list);
    }

    /**
     * Instantiates a list of {@link InputMethodSubtype} from the given
     * {@link InputMethodSubtypeSafeList} then clears the internal buffer of
     * {@link InputMethodSubtypeSafeList}.
     *
     * <p>Note that each {@link InputMethodSubtype} item is guaranteed to be a copy of the original
     * {@link InputMethodSubtype} object.</p>
     *
     * <p>Any subsequent call will return an empty list.</p>
     *
     * @param from {@link InputMethodSubtypeSafeList} from which the list of
     *             {@link InputMethodSubtype} will be extracted
     * @return list of {@link InputMethodSubtype} stored in the given
     *         {@link InputMethodSubtypeSafeList}
     */
    @NonNull
    public static List<InputMethodSubtype> extractFrom(@Nullable InputMethodSubtypeSafeList from) {
        return AbstractSafeList.extractFrom(from, InputMethodSubtype.CREATOR);
    }

    /**
     * Instantiates {@link InputMethodSubtypeSafeList} from the given list of
     * {@link InputMethodSubtype}.
     *
     * @param list list of {@link InputMethodSubtype} from which
     *             {@link InputMethodSubtypeSafeList} will be created. Giving {@code null} will
     *             result in an empty {@link InputMethodSubtypeSafeList}.
     * @return {@link InputMethodSubtypeSafeList} that stores the given list of
     *         {@link InputMethodSubtype}
     */
    @NonNull
    public static InputMethodSubtypeSafeList create(@Nullable List<InputMethodSubtype> list) {
        return new InputMethodSubtypeSafeList(list);
    }

    public static final Creator<InputMethodSubtypeSafeList> CREATOR = new Creator<>() {
        @Override
        public InputMethodSubtypeSafeList createFromParcel(Parcel in) {
            return new InputMethodSubtypeSafeList(in.readBlob());
        }

        @Override
        public InputMethodSubtypeSafeList[] newArray(int size) {
            return new InputMethodSubtypeSafeList[size];
        }
    };
}
Loading