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

Commit 5a0d9cf7 authored by Yohei Yukawa's avatar Yohei Yukawa Committed by android-build-merger
Browse files

Merge "Tell IMS about missing InputConnection methods." into nyc-dev

am: 1064d353

* commit '1064d353':
  Tell IMS about missing InputConnection methods.
parents e6fd75c7 1064d353
Loading
Loading
Loading
Loading
+18 −9
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
@@ -164,9 +165,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
                return;
            case DO_START_INPUT: {
                SomeArgs args = (SomeArgs)msg.obj;
                int missingMethods = msg.arg1;
                IInputContext inputContext = (IInputContext)args.arg1;
                InputConnection ic = inputContext != null
                        ? new InputConnectionWrapper(inputContext) : null;
                        ? new InputConnectionWrapper(inputContext, missingMethods) : null;
                EditorInfo info = (EditorInfo)args.arg2;
                info.makeCompatible(mTargetSdkVersion);
                inputMethod.startInput(ic, info);
@@ -175,9 +177,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
            }
            case DO_RESTART_INPUT: {
                SomeArgs args = (SomeArgs)msg.obj;
                int missingMethods = msg.arg1;
                IInputContext inputContext = (IInputContext)args.arg1;
                InputConnection ic = inputContext != null
                        ? new InputConnectionWrapper(inputContext) : null;
                        ? new InputConnectionWrapper(inputContext, missingMethods) : null;
                EditorInfo info = (EditorInfo)args.arg2;
                info.makeCompatible(mTargetSdkVersion);
                inputMethod.restartInput(ic, info);
@@ -246,8 +249,10 @@ class IInputMethodWrapper extends IInputMethod.Stub

    @Override
    public void bindInput(InputBinding binding) {
        // This IInputContext is guaranteed to implement all the methods.
        final int missingMethodFlags = 0;
        InputConnection ic = new InputConnectionWrapper(
                IInputContext.Stub.asInterface(binding.getConnectionToken()));
                IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
        InputBinding nu = new InputBinding(ic, binding);
        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
    }
@@ -258,15 +263,19 @@ class IInputMethodWrapper extends IInputMethod.Stub
    }

    @Override
    public void startInput(IInputContext inputContext, EditorInfo attribute) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
                inputContext, attribute));
    public void startInput(IInputContext inputContext,
            @InputConnectionInspector.MissingMethodFlags final int missingMethods,
            EditorInfo attribute) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_START_INPUT,
                missingMethods, inputContext, attribute));
    }

    @Override
    public void restartInput(IInputContext inputContext, EditorInfo attribute) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
                inputContext, attribute));
    public void restartInput(IInputContext inputContext,
            @InputConnectionInspector.MissingMethodFlags final int missingMethods,
            EditorInfo attribute) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_RESTART_INPUT,
                missingMethods, inputContext, attribute));
    }

    @Override
+29 −7
Original line number Diff line number Diff line
@@ -28,10 +28,24 @@ import android.view.KeyEvent;
 * cursor, committing text to the text box, and sending raw key events
 * to the application.
 *
 * <p>Applications should never directly implement this interface, but
 * instead subclass from {@link BaseInputConnection}. This will ensure
 * that the application does not break when new methods are added to
 * the interface.</p>
 * <p>Starting from API Level {@link android.os.Build.VERSION_CODES#N},
 * the system can deal with the situation where the application directly
 * implements this class but one or more of the following methods are
 * not implemented.</p>
 * <ul>
 *     <li>{@link #getSelectedText(int)}, which was introduced in
 *     {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
 *     <li>{@link #setComposingRegion(int, int)}, which was introduced
 *     in {@link android.os.Build.VERSION_CODES#GINGERBREAD}.</li>
 *     <li>{@link #commitCorrection(CorrectionInfo)}, which was introduced
 *     in {@link android.os.Build.VERSION_CODES#HONEYCOMB}.</li>
 *     <li>{@link #requestCursorUpdates(int)}, which was introduced in
 *     {@link android.os.Build.VERSION_CODES#LOLLIPOP}.</li>
 *     <li>{@link #deleteSurroundingTextInCodePoints(int, int)}}, which
 *     was introduced in {@link android.os.Build.VERSION_CODES#N}.</li>
 *     <li>{@link #getHandler()}}, which was introduced in
 *     {@link android.os.Build.VERSION_CODES#N}.</li>
 * </ul>
 *
 * <h3>Implementing an IME or an editor</h3>
 * <p>Text input is the result of the synergy of two essential components:
@@ -224,7 +238,9 @@ public interface InputConnection {
     * @param flags Supplies additional options controlling how the text is
     * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
     * @return the text that is currently selected, if any, or null if
     * no text is selected.
     * no text is selected. In {@link android.os.Build.VERSION_CODES#N} and
     * later, returns false when the target application does not implement
     * this method.
     */
    public CharSequence getSelectedText(int flags);

@@ -371,7 +387,8 @@ public interface InputConnection {
     *        If this is greater than the number of existing characters between the cursor and
     *        the end of the text, then this method does not fail but deletes all the characters in
     *        that range.
     * @return true on success, false if the input connection is no longer valid.
     * @return true on success, false if the input connection is no longer valid.  Returns
     * {@code false} when the target application does not implement this method.
     */
    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);

@@ -461,7 +478,8 @@ public interface InputConnection {
     * @param start the position in the text at which the composing region begins
     * @param end the position in the text at which the composing region ends
     * @return true on success, false if the input connection is no longer
     * valid.
     * valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
     * target application does not implement this method.
     */
    public boolean setComposingRegion(int start, int end);

@@ -573,6 +591,8 @@ public interface InputConnection {
     *
     * @param correctionInfo Detailed information about the correction.
     * @return true on success, false if the input connection is no longer valid.
     * In {@link android.os.Build.VERSION_CODES#N} and later, returns false
     * when the target application does not implement this method.
     */
    public boolean commitCorrection(CorrectionInfo correctionInfo);

@@ -785,6 +805,8 @@ public interface InputConnection {
     * @return {@code true} if the request is scheduled. {@code false} to indicate that when the
     * application will not call
     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}.
     * In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
     * target application does not implement this method.
     */
    public boolean requestCursorUpdates(int cursorUpdateMode);

+224 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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 android.view.inputmethod;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;

import java.lang.annotation.Retention;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;

import static java.lang.annotation.RetentionPolicy.SOURCE;

/**
 * @hide
 */
public final class InputConnectionInspector {

    @Retention(SOURCE)
    @IntDef({MissingMethodFlags.GET_SELECTED_TEXT,
            MissingMethodFlags.SET_COMPOSING_REGION,
            MissingMethodFlags.COMMIT_CORRECTION,
            MissingMethodFlags.REQUEST_CURSOR_UPDATES,
            MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
            MissingMethodFlags.GET_HANDLER,
    })
    public @interface MissingMethodFlags {
        /**
         * {@link InputConnection#getSelectedText(int)} is available in
         * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
         */
        int GET_SELECTED_TEXT = 1 << 0;
        /**
         * {@link InputConnection#setComposingRegion(int, int)} is available in
         * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
         */
        int SET_COMPOSING_REGION = 1 << 1;
        /**
         * {@link InputConnection#commitCorrection(CorrectionInfo)} is available in
         * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and later.
         */
        int COMMIT_CORRECTION = 1 << 2;
        /**
         * {@link InputConnection#requestCursorUpdates(int)} is available in
         * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
         */
        int REQUEST_CURSOR_UPDATES = 1 << 3;
        /**
         * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
         * {@link android.os.Build.VERSION_CODES#N} and later.
         */
        int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 1 << 4;
        /**
         * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
         * {@link android.os.Build.VERSION_CODES#N} and later.
         */
        int GET_HANDLER = 1 << 5;
    }

    private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
            new WeakHashMap<>());

    @MissingMethodFlags
    public static int getMissingMethodFlags(@Nullable final InputConnection ic) {
        if (ic == null) {
            return 0;
        }
        // Optimization for a known class.
        if (ic instanceof BaseInputConnection) {
            return 0;
        }
        // Optimization for a known class.
        if (ic instanceof InputConnectionWrapper) {
            return ((InputConnectionWrapper) ic).getMissingMethodFlags();
        }
        return getMissingMethodFlagsInternal(ic.getClass());
    }

    @MissingMethodFlags
    public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
        final Integer cachedFlags = sMissingMethodsMap.get(clazz);
        if (cachedFlags != null) {
            return cachedFlags;
        }
        int flags = 0;
        if (!hasGetSelectedText(clazz)) {
            flags |= MissingMethodFlags.GET_SELECTED_TEXT;
        }
        if (!hasSetComposingRegion(clazz)) {
            flags |= MissingMethodFlags.SET_COMPOSING_REGION;
        }
        if (!hasCommitCorrection(clazz)) {
            flags |= MissingMethodFlags.COMMIT_CORRECTION;
        }
        if (!hasRequestCursorUpdate(clazz)) {
            flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES;
        }
        if (!hasDeleteSurroundingTextInCodePoints(clazz)) {
            flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS;
        }
        if (!hasGetHandler(clazz)) {
            flags |= MissingMethodFlags.GET_HANDLER;
        }
        sMissingMethodsMap.put(clazz, flags);
        return flags;
    }

    private static boolean hasGetSelectedText(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("getSelectedText", int.class);
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static boolean hasSetComposingRegion(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("setComposingRegion", int.class, int.class);
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static boolean hasCommitCorrection(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class);
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("requestCursorUpdates", int.class);
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class,
                    int.class);
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static boolean hasGetHandler(@NonNull final Class clazz) {
        try {
            final Method method = clazz.getMethod("getHandler");
            return !Modifier.isAbstract(method.getModifiers());
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
        final StringBuilder sb = new StringBuilder();
        boolean isEmpty = true;
        if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) {
            sb.append("getSelectedText(int)");
            isEmpty = false;
        }
        if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) {
            if (!isEmpty) {
                sb.append(",");
            }
            sb.append("setComposingRegion(int, int)");
            isEmpty = false;
        }
        if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) {
            if (!isEmpty) {
                sb.append(",");
            }
            sb.append("commitCorrection(CorrectionInfo)");
            isEmpty = false;
        }
        if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) {
            if (!isEmpty) {
                sb.append(",");
            }
            sb.append("requestCursorUpdate(int)");
            isEmpty = false;
        }
        if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) {
            if (!isEmpty) {
                sb.append(",");
            }
            sb.append("deleteSurroundingTextInCodePoints(int, int)");
            isEmpty = false;
        }
        if ((flags & MissingMethodFlags.GET_HANDLER) != 0) {
            if (!isEmpty) {
                sb.append(",");
            }
            sb.append("getHandler()");
        }
        return sb.toString();
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.view.KeyEvent;
public class InputConnectionWrapper implements InputConnection {
    private InputConnection mTarget;
    final boolean mMutable;
    @InputConnectionInspector.MissingMethodFlags
    private int mMissingMethodFlags;

    /**
     * Initializes a wrapper.
@@ -40,6 +42,7 @@ public class InputConnectionWrapper implements InputConnection {
    public InputConnectionWrapper(InputConnection target, boolean mutable) {
        mMutable = mutable;
        mTarget = target;
        mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
    }

    /**
@@ -56,6 +59,15 @@ public class InputConnectionWrapper implements InputConnection {
            throw new SecurityException("not mutable");
        }
        mTarget = target;
        mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target);
    }

    /**
     * @hide
     */
    @InputConnectionInspector.MissingMethodFlags
    public int getMissingMethodFlags() {
        return mMissingMethodFlags;
    }

    /**
+13 −3
Original line number Diff line number Diff line
@@ -1224,6 +1224,7 @@ public final class InputMethodManager {
            notifyInputConnectionFinished();
            mServedInputConnection = ic;
            ControlledInputConnectionWrapper servedContext;
            final int missingMethodFlags;
            if (ic != null) {
                mCursorSelStart = tba.initialSelStart;
                mCursorSelEnd = tba.initialSelEnd;
@@ -1231,11 +1232,20 @@ public final class InputMethodManager {
                mCursorCandEnd = -1;
                mCursorRect.setEmpty();
                mCursorAnchorInfo = null;
                final Handler icHandler = ic.getHandler();
                final Handler icHandler;
                missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
                if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
                        != 0) {
                    // InputConnection#getHandler() is not implemented.
                    icHandler = null;
                } else {
                    icHandler = ic.getHandler();
                }
                servedContext = new ControlledInputConnectionWrapper(
                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
            } else {
                servedContext = null;
                missingMethodFlags = 0;
            }
            if (mServedInputConnectionWrapper != null) {
                mServedInputConnectionWrapper.deactivate();
@@ -1248,7 +1258,7 @@ public final class InputMethodManager {
                        + Integer.toHexString(controlFlags));
                final InputBindResult res = mService.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                        windowFlags, tba, servedContext);
                        windowFlags, tba, servedContext, missingMethodFlags);
                if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                if (res != null) {
                    if (res.id != null) {
@@ -1476,7 +1486,7 @@ public final class InputMethodManager {
                mService.startInputOrWindowGainedFocus(
                        InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
                        rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null,
                        null);
                        null, 0 /* missingMethodFlags */);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
Loading