Loading core/java/android/inputmethodservice/IInputMethodWrapper.java +18 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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)); } Loading @@ -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 Loading core/java/android/view/inputmethod/InputConnection.java +29 −7 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading core/java/android/view/inputmethod/InputConnectionInspector.java 0 → 100644 +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(); } } core/java/android/view/inputmethod/InputConnectionWrapper.java +12 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -40,6 +42,7 @@ public class InputConnectionWrapper implements InputConnection { public InputConnectionWrapper(InputConnection target, boolean mutable) { mMutable = mutable; mTarget = target; mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target); } /** Loading @@ -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; } /** Loading core/java/android/view/inputmethod/InputMethodManager.java +13 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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) { Loading Loading @@ -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 Loading
core/java/android/inputmethodservice/IInputMethodWrapper.java +18 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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)); } Loading @@ -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 Loading
core/java/android/view/inputmethod/InputConnection.java +29 −7 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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); Loading
core/java/android/view/inputmethod/InputConnectionInspector.java 0 → 100644 +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(); } }
core/java/android/view/inputmethod/InputConnectionWrapper.java +12 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -40,6 +42,7 @@ public class InputConnectionWrapper implements InputConnection { public InputConnectionWrapper(InputConnection target, boolean mutable) { mMutable = mutable; mTarget = target; mMissingMethodFlags = InputConnectionInspector.getMissingMethodFlags(target); } /** Loading @@ -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; } /** Loading
core/java/android/view/inputmethod/InputMethodManager.java +13 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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) { Loading Loading @@ -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