| /* |
| * Copyright (C) 2021 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 static java.lang.annotation.RetentionPolicy.SOURCE; |
| |
| import android.annotation.AnyThread; |
| import android.annotation.IntDef; |
| import android.annotation.IntRange; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.inputmethod.CompletionInfo; |
| import android.view.inputmethod.CorrectionInfo; |
| import android.view.inputmethod.ExtractedTextRequest; |
| import android.view.inputmethod.InputContentInfo; |
| |
| import java.lang.annotation.Retention; |
| |
| /** |
| * Defines the command message to be used for IMEs to remotely invoke |
| * {@link android.view.inputmethod.InputConnection} APIs in the IME client process then receive |
| * results. |
| */ |
| public final class InputConnectionCommand implements Parcelable { |
| private static final String TAG = "InputConnectionCommand"; |
| |
| @Retention(SOURCE) |
| @IntDef(value = { |
| ResultCallbackType.NULL, |
| ResultCallbackType.BOOLEAN, |
| ResultCallbackType.INT, |
| ResultCallbackType.CHAR_SEQUENCE, |
| ResultCallbackType.EXTRACTED_TEXT, |
| ResultCallbackType.SURROUNDING_TEXT, |
| }) |
| @interface ResultCallbackType { |
| int NULL = 0; |
| int BOOLEAN = 1; |
| int INT = 2; |
| int CHAR_SEQUENCE = 3; |
| int EXTRACTED_TEXT = 4; |
| int SURROUNDING_TEXT = 5; |
| } |
| |
| @Retention(SOURCE) |
| @IntDef(value = { |
| ParcelableType.NULL, |
| ParcelableType.EXTRACTED_TEXT_REQUEST, |
| ParcelableType.COMPLETION_INFO, |
| ParcelableType.CORRECTION_INFO, |
| ParcelableType.KEY_EVENT, |
| ParcelableType.INPUT_CONTENT_INFO, |
| }) |
| @interface ParcelableType { |
| int NULL = 0; |
| int EXTRACTED_TEXT_REQUEST = 1; |
| int COMPLETION_INFO = 2; |
| int CORRECTION_INFO = 3; |
| int KEY_EVENT = 4; |
| int INPUT_CONTENT_INFO = 5; |
| } |
| |
| @Retention(SOURCE) |
| @IntDef(flag = true, value = { |
| FieldMask.INT_ARG0, |
| FieldMask.INT_ARG1, |
| FieldMask.FLAGS, |
| FieldMask.CHAR_SEQUENCE, |
| FieldMask.STRING, |
| FieldMask.BUNDLE, |
| FieldMask.PARCELABLE, |
| FieldMask.CALLBACK, |
| }) |
| @interface FieldMask { |
| int INT_ARG0 = 1 << 0; |
| int INT_ARG1 = 1 << 1; |
| int FLAGS = 1 << 2; |
| int CHAR_SEQUENCE = 1 << 3; |
| int STRING = 1 << 4; |
| int BUNDLE = 1 << 5; |
| int PARCELABLE = 1 << 6; |
| int CALLBACK = 1 << 7; |
| } |
| |
| @IntRange(from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType |
| public final int mCommandType; |
| public final int mIntArg0; |
| public final int mIntArg1; |
| public final int mFlags; |
| public final CharSequence mCharSequence; |
| public final String mString; |
| public final Bundle mBundle; |
| @ParcelableType |
| public final int mParcelableType; |
| public final Parcelable mParcelable; |
| @ResultCallbackType |
| public final int mResultCallbackType; |
| public final IBinder mResultCallback; |
| |
| private InputConnectionCommand( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, int intArg0, int intArg1, int flags, |
| @Nullable CharSequence charSequence, @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @ResultCallbackType int resultCallbackType, @Nullable IBinder resultCallback) { |
| if (type < InputConnectionCommandType.FIRST_COMMAND |
| || InputConnectionCommandType.LAST_COMMAND < type) { |
| throw new IllegalArgumentException("Unknown type=" + type); |
| } |
| mCommandType = type; |
| mIntArg0 = intArg0; |
| mIntArg1 = intArg1; |
| mFlags = flags; |
| mCharSequence = charSequence; |
| mString = string; |
| mBundle = bundle; |
| mParcelableType = parcelableType; |
| mParcelable = parcelable; |
| mResultCallbackType = resultCallbackType; |
| mResultCallback = resultCallback; |
| } |
| |
| /** |
| * Creates {@link InputConnectionCommand} with the given {@link InputConnectionCommandType}. |
| * |
| * @param type {@link InputConnectionCommandType} to be set. |
| * @return An {@link InputConnectionCommand} that is initialized with {@code type}. |
| */ |
| @NonNull |
| public static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type) { |
| return create(type, 0); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, int intArg0) { |
| return create(type, intArg0, 0); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, int intArg0, int intArg1) { |
| return create(type, intArg0, intArg1, 0, null); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, int intArg0, |
| int intArg1, int flags, @Nullable CharSequence charSequence) { |
| return create(type, intArg0, intArg1, flags, charSequence, null, null); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle) { |
| return create(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, ParcelableType.NULL, null); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, ResultCallbackType.NULL, null); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @NonNull Completable.Boolean returnValue) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, |
| ResultCallbackType.BOOLEAN, ResultCallbacks.of(returnValue)); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @NonNull Completable.Int returnValue) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, |
| ResultCallbackType.INT, ResultCallbacks.of(returnValue)); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @NonNull Completable.CharSequence returnValue) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, |
| ResultCallbackType.CHAR_SEQUENCE, ResultCallbacks.of(returnValue)); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @NonNull Completable.ExtractedText returnValue) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, |
| ResultCallbackType.EXTRACTED_TEXT, ResultCallbacks.of(returnValue)); |
| } |
| |
| @NonNull |
| static InputConnectionCommand create( |
| @IntRange( |
| from = InputConnectionCommandType.FIRST_COMMAND, |
| to = InputConnectionCommandType.LAST_COMMAND) |
| @InputConnectionCommandType int type, |
| int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, |
| @Nullable String string, @Nullable Bundle bundle, |
| @ParcelableType int parcelableType, @Nullable Parcelable parcelable, |
| @NonNull Completable.SurroundingText returnValue) { |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, |
| ResultCallbackType.SURROUNDING_TEXT, ResultCallbacks.of(returnValue)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @AnyThread |
| @Override |
| public int describeContents() { |
| int result = 0; |
| if (mBundle != null) { |
| result |= mBundle.describeContents(); |
| } |
| if (mParcelable != null) { |
| result |= mParcelable.describeContents(); |
| } |
| // Here we assume other objects will never contain FDs to be parcelled. |
| return result; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @AnyThread |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mCommandType); |
| |
| @FieldMask final int fieldMask = getFieldMask(); |
| dest.writeInt(fieldMask); |
| if ((fieldMask & FieldMask.INT_ARG0) != 0) { |
| dest.writeInt(mIntArg0); |
| } |
| if ((fieldMask & FieldMask.INT_ARG1) != 0) { |
| dest.writeInt(mIntArg1); |
| } |
| if ((fieldMask & FieldMask.FLAGS) != 0) { |
| dest.writeInt(mFlags); |
| } |
| if ((fieldMask & FieldMask.CHAR_SEQUENCE) != 0) { |
| TextUtils.writeToParcel(mCharSequence, dest, flags); |
| } |
| if ((fieldMask & FieldMask.STRING) != 0) { |
| dest.writeString(mString); |
| } |
| if ((fieldMask & FieldMask.BUNDLE) != 0) { |
| dest.writeBundle(mBundle); |
| } |
| if ((fieldMask & FieldMask.PARCELABLE) != 0) { |
| dest.writeInt(mParcelableType); |
| dest.writeTypedObject(mParcelable, flags); |
| } |
| if ((fieldMask & FieldMask.CALLBACK) != 0) { |
| dest.writeInt(mResultCallbackType); |
| dest.writeStrongBinder(mResultCallback); |
| } |
| } |
| |
| @FieldMask |
| @AnyThread |
| private int getFieldMask() { |
| return (mIntArg0 != 0 ? FieldMask.INT_ARG0 : 0) |
| | (mIntArg1 != 0 ? FieldMask.INT_ARG1 : 0) |
| | (mFlags != 0 ? FieldMask.FLAGS : 0) |
| | (mCharSequence != null ? FieldMask.CHAR_SEQUENCE : 0) |
| | (mString != null ? FieldMask.STRING : 0) |
| | (mBundle != null ? FieldMask.BUNDLE : 0) |
| | (mParcelableType != ParcelableType.NULL ? FieldMask.PARCELABLE : 0) |
| | (mResultCallbackType != ResultCallbackType.NULL ? FieldMask.CALLBACK : 0); |
| } |
| |
| @AnyThread |
| @Nullable |
| private static InputConnectionCommand createFromParcel(@NonNull Parcel source) { |
| final int type = source.readInt(); |
| if (type < InputConnectionCommandType.FIRST_COMMAND |
| || InputConnectionCommandType.LAST_COMMAND < type) { |
| Log.e(TAG, "Invalid InputConnectionCommand type=" + type); |
| return null; |
| } |
| |
| @FieldMask final int fieldMask = source.readInt(); |
| final int intArg0 = (fieldMask & FieldMask.INT_ARG0) != 0 ? source.readInt() : 0; |
| final int intArg1 = (fieldMask & FieldMask.INT_ARG1) != 0 ? source.readInt() : 0; |
| final int flags = (fieldMask & FieldMask.FLAGS) != 0 ? source.readInt() : 0; |
| final CharSequence charSequence = (fieldMask & FieldMask.CHAR_SEQUENCE) != 0 |
| ? TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source) : null; |
| final String string = (fieldMask & FieldMask.STRING) != 0 ? source.readString() : null; |
| final Bundle bundle = (fieldMask & FieldMask.BUNDLE) != 0 ? source.readBundle() : null; |
| |
| @ParcelableType final int parcelableType; |
| final Parcelable parcelable; |
| if ((fieldMask & FieldMask.PARCELABLE) != 0) { |
| parcelableType = source.readInt(); |
| switch (parcelableType) { |
| case ParcelableType.NULL: |
| Log.e(TAG, "Unexpected ParcelableType=NULL"); |
| return null; |
| case ParcelableType.EXTRACTED_TEXT_REQUEST: |
| parcelable = source.readTypedObject(ExtractedTextRequest.CREATOR); |
| break; |
| case ParcelableType.COMPLETION_INFO: |
| parcelable = source.readTypedObject(CompletionInfo.CREATOR); |
| break; |
| case ParcelableType.CORRECTION_INFO: |
| parcelable = source.readTypedObject(CorrectionInfo.CREATOR); |
| break; |
| case ParcelableType.KEY_EVENT: |
| parcelable = source.readTypedObject(KeyEvent.CREATOR); |
| break; |
| case ParcelableType.INPUT_CONTENT_INFO: |
| parcelable = source.readTypedObject(InputContentInfo.CREATOR); |
| break; |
| default: |
| Log.e(TAG, "Unknown ParcelableType=" + parcelableType); |
| return null; |
| } |
| } else { |
| parcelableType = ParcelableType.NULL; |
| parcelable = null; |
| } |
| @ResultCallbackType final int resultCallbackType; |
| final IBinder resultCallback; |
| if ((fieldMask & FieldMask.CALLBACK) != 0) { |
| resultCallbackType = source.readInt(); |
| switch (resultCallbackType) { |
| case ResultCallbackType.NULL: |
| Log.e(TAG, "Unexpected ResultCallbackType=NULL"); |
| return null; |
| case ResultCallbackType.BOOLEAN: |
| case ResultCallbackType.INT: |
| case ResultCallbackType.CHAR_SEQUENCE: |
| case ResultCallbackType.EXTRACTED_TEXT: |
| case ResultCallbackType.SURROUNDING_TEXT: |
| resultCallback = source.readStrongBinder(); |
| break; |
| default: |
| Log.e(TAG, "Unknown ResultCallbackType=" + resultCallbackType); |
| return null; |
| } |
| } else { |
| resultCallbackType = ResultCallbackType.NULL; |
| resultCallback = null; |
| } |
| return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, |
| bundle, parcelableType, parcelable, resultCallbackType, resultCallback); |
| } |
| |
| /** |
| * Used to make this class parcelable. |
| */ |
| public static final Parcelable.Creator<InputConnectionCommand> CREATOR = |
| new Parcelable.Creator<InputConnectionCommand>() { |
| @AnyThread |
| @Nullable |
| @Override |
| public InputConnectionCommand createFromParcel(Parcel source) { |
| try { |
| return InputConnectionCommand.createFromParcel(source); |
| } catch (Exception e) { |
| Log.e(TAG, "Returning null due to exception.", e); |
| return null; |
| } |
| } |
| |
| @AnyThread |
| @NonNull |
| @Override |
| public InputConnectionCommand[] newArray(int size) { |
| return new InputConnectionCommand[size]; |
| } |
| }; |
| } |