Merge "Tell IMS about missing InputConnection methods." into nyc-dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
core/java/android/view/inputmethod/InputConnectionInspector.java
Normal file
224
core/java/android/view/inputmethod/InputConnectionInspector.java
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ oneway interface IInputMethod {
|
||||
|
||||
void unbindInput();
|
||||
|
||||
void startInput(in IInputContext inputContext, in EditorInfo attribute);
|
||||
void startInput(in IInputContext inputContext, int missingMethods, in EditorInfo attribute);
|
||||
|
||||
void restartInput(in IInputContext inputContext, in EditorInfo attribute);
|
||||
void restartInput(in IInputContext inputContext, int missingMethods, in EditorInfo attribute);
|
||||
|
||||
void createSession(in InputChannel channel, IInputSessionCallback callback);
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ interface IInputMethodManager {
|
||||
/* @InputMethodClient.StartInputReason */ int startInputReason,
|
||||
in IInputMethodClient client, in IBinder windowToken, int controlFlags,
|
||||
int softInputMode, int windowFlags, in EditorInfo attribute,
|
||||
IInputContext inputContext);
|
||||
IInputContext inputContext,
|
||||
/* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags);
|
||||
|
||||
void showInputMethodPickerFromClient(in IInputMethodClient client,
|
||||
int auxiliarySubtypeMode);
|
||||
|
||||
@@ -27,11 +27,15 @@ import android.view.inputmethod.CorrectionInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputConnectionInspector;
|
||||
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
|
||||
|
||||
public class InputConnectionWrapper implements InputConnection {
|
||||
private static final int MAX_WAIT_TIME_MILLIS = 2000;
|
||||
private final IInputContext mIInputContext;
|
||||
|
||||
@MissingMethodFlags
|
||||
private final int mMissingMethods;
|
||||
|
||||
static class InputContextCallback extends IInputContextCallback.Stub {
|
||||
private static final String TAG = "InputConnectionWrapper.ICC";
|
||||
public int mSeq;
|
||||
@@ -191,8 +195,10 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public InputConnectionWrapper(IInputContext inputContext) {
|
||||
public InputConnectionWrapper(IInputContext inputContext,
|
||||
@MissingMethodFlags final int missingMethods) {
|
||||
mIInputContext = inputContext;
|
||||
mMissingMethods = missingMethods;
|
||||
}
|
||||
|
||||
public CharSequence getTextAfterCursor(int length, int flags) {
|
||||
@@ -230,8 +236,12 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public CharSequence getSelectedText(int flags) {
|
||||
if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
|
||||
// This method is not implemented.
|
||||
return null;
|
||||
}
|
||||
CharSequence value = null;
|
||||
try {
|
||||
InputContextCallback callback = InputContextCallback.getInstance();
|
||||
@@ -295,6 +305,10 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
}
|
||||
|
||||
public boolean commitCompletion(CompletionInfo text) {
|
||||
if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
|
||||
// This method is not implemented.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
mIInputContext.commitCompletion(text);
|
||||
return true;
|
||||
@@ -340,6 +354,10 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
}
|
||||
|
||||
public boolean setComposingRegion(int start, int end) {
|
||||
if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
|
||||
// This method is not implemented.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
mIInputContext.setComposingRegion(start, end);
|
||||
return true;
|
||||
@@ -412,6 +430,10 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
}
|
||||
|
||||
public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
|
||||
if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
|
||||
// This method is not implemented.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
|
||||
return true;
|
||||
@@ -440,6 +462,10 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
|
||||
public boolean requestCursorUpdates(int cursorUpdateMode) {
|
||||
boolean result = false;
|
||||
if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
|
||||
// This method is not implemented.
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
InputContextCallback callback = InputContextCallback.getInstance();
|
||||
mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
|
||||
@@ -460,4 +486,16 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
// Nothing should happen when called from input method.
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
|
||||
return (mMissingMethods & methodFlag) == methodFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InputConnectionWrapper{idHash=#"
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " mMissingMethods="
|
||||
+ InputConnectionInspector.getMissingMethodFlagsAsString(mMissingMethods) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ import android.view.WindowManager;
|
||||
import android.view.WindowManagerInternal;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputBinding;
|
||||
import android.view.inputmethod.InputConnectionInspector;
|
||||
import android.view.inputmethod.InputMethod;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
@@ -326,6 +327,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
*/
|
||||
IInputContext mCurInputContext;
|
||||
|
||||
/**
|
||||
* The missing method flags for the input context last provided by the current client.
|
||||
*
|
||||
* @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
|
||||
*/
|
||||
int mCurInputContextMissingMethods;
|
||||
|
||||
/**
|
||||
* The attributes last provided by the current client.
|
||||
*/
|
||||
@@ -1289,11 +1297,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
final SessionState session = mCurClient.curSession;
|
||||
if (initial) {
|
||||
executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
|
||||
MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
|
||||
executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
|
||||
MSG_START_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
|
||||
mCurAttribute));
|
||||
} else {
|
||||
executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
|
||||
MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
|
||||
executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
|
||||
MSG_RESTART_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
|
||||
mCurAttribute));
|
||||
}
|
||||
if (mShowRequested) {
|
||||
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
|
||||
@@ -1306,7 +1316,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
InputBindResult startInputLocked(
|
||||
/* @InputMethodClient.StartInputReason */ final int startInputReason,
|
||||
IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
|
||||
IInputMethodClient client, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
|
||||
EditorInfo attribute,
|
||||
int controlFlags) {
|
||||
// If no method is currently selected, do nothing.
|
||||
if (mCurMethodId == null) {
|
||||
@@ -1333,10 +1345,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
|
||||
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
|
||||
return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
|
||||
controlFlags);
|
||||
}
|
||||
|
||||
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
|
||||
@NonNull EditorInfo attribute, int controlFlags) {
|
||||
// If no method is currently selected, do nothing.
|
||||
if (mCurMethodId == null) {
|
||||
@@ -1371,6 +1385,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
if (mCurSeq <= 0) mCurSeq = 1;
|
||||
mCurClient = cs;
|
||||
mCurInputContext = inputContext;
|
||||
mCurInputContextMissingMethods = missingMethods;
|
||||
mCurAttribute = attribute;
|
||||
|
||||
// Check if the input method is changing.
|
||||
@@ -1459,8 +1474,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
private InputBindResult startInput(
|
||||
/* @InputMethodClient.StartInputReason */ final int startInputReason,
|
||||
IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
|
||||
int controlFlags) {
|
||||
IInputMethodClient client, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.missingMethods */ final int missingMethods,
|
||||
EditorInfo attribute, int controlFlags) {
|
||||
if (!calledFromValidUser()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1470,13 +1486,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
+ InputMethodClient.getStartInputReason(startInputReason)
|
||||
+ " client = " + client.asBinder()
|
||||
+ " inputContext=" + inputContext
|
||||
+ " missingMethods="
|
||||
+ InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
|
||||
+ " attribute=" + attribute
|
||||
+ " controlFlags=#" + Integer.toHexString(controlFlags));
|
||||
}
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return startInputLocked(startInputReason, client, inputContext, attribute,
|
||||
controlFlags);
|
||||
return startInputLocked(startInputReason, client, inputContext, missingMethods,
|
||||
attribute, controlFlags);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
@@ -2190,19 +2208,22 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
public InputBindResult startInputOrWindowGainedFocus(
|
||||
/* @InputMethodClient.StartInputReason */ final int startInputReason,
|
||||
IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext) {
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.missingMethods */ final int missingMethods) {
|
||||
if (windowToken != null) {
|
||||
return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
|
||||
softInputMode, windowFlags, attribute, inputContext);
|
||||
softInputMode, windowFlags, attribute, inputContext, missingMethods);
|
||||
} else {
|
||||
return startInput(startInputReason, client, inputContext, attribute, controlFlags);
|
||||
return startInput(startInputReason, client, inputContext, missingMethods, attribute,
|
||||
controlFlags);
|
||||
}
|
||||
}
|
||||
|
||||
private InputBindResult windowGainedFocus(
|
||||
/* @InputMethodClient.StartInputReason */ final int startInputReason,
|
||||
IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext) {
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.missingMethods */ final int missingMethods) {
|
||||
// Needs to check the validity before clearing calling identity
|
||||
final boolean calledFromValidUser = calledFromValidUser();
|
||||
InputBindResult res = null;
|
||||
@@ -2213,6 +2234,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
+ InputMethodClient.getStartInputReason(startInputReason)
|
||||
+ " client=" + client.asBinder()
|
||||
+ " inputContext=" + inputContext
|
||||
+ " missingMethods="
|
||||
+ InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
|
||||
+ " attribute=" + attribute
|
||||
+ " controlFlags=#" + Integer.toHexString(controlFlags)
|
||||
+ " softInputMode=#" + Integer.toHexString(softInputMode)
|
||||
@@ -2250,8 +2273,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
|
||||
+ " attribute=" + attribute + ", token = " + windowToken);
|
||||
if (attribute != null) {
|
||||
return startInputUncheckedLocked(cs, inputContext, attribute,
|
||||
controlFlags);
|
||||
return startInputUncheckedLocked(cs, inputContext, missingMethods,
|
||||
attribute, controlFlags);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -2300,8 +2323,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
// is more room for the target window + IME.
|
||||
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
|
||||
if (attribute != null) {
|
||||
res = startInputUncheckedLocked(cs, inputContext, attribute,
|
||||
controlFlags);
|
||||
res = startInputUncheckedLocked(cs, inputContext,
|
||||
missingMethods, attribute, controlFlags);
|
||||
didStart = true;
|
||||
}
|
||||
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
|
||||
@@ -2326,8 +2349,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
|
||||
if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
|
||||
if (attribute != null) {
|
||||
res = startInputUncheckedLocked(cs, inputContext, attribute,
|
||||
controlFlags);
|
||||
res = startInputUncheckedLocked(cs, inputContext,
|
||||
missingMethods, attribute, controlFlags);
|
||||
didStart = true;
|
||||
}
|
||||
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
|
||||
@@ -2336,8 +2359,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
|
||||
if (DEBUG) Slog.v(TAG, "Window asks to always show input");
|
||||
if (attribute != null) {
|
||||
res = startInputUncheckedLocked(cs, inputContext, attribute,
|
||||
controlFlags);
|
||||
res = startInputUncheckedLocked(cs, inputContext, missingMethods,
|
||||
attribute, controlFlags);
|
||||
didStart = true;
|
||||
}
|
||||
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
|
||||
@@ -2345,7 +2368,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
|
||||
if (!didStart && attribute != null) {
|
||||
res = startInputUncheckedLocked(cs, inputContext, attribute,
|
||||
res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
|
||||
controlFlags);
|
||||
}
|
||||
}
|
||||
@@ -2807,28 +2830,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
// ---------------------------------------------------------
|
||||
|
||||
case MSG_START_INPUT:
|
||||
args = (SomeArgs)msg.obj;
|
||||
case MSG_START_INPUT: {
|
||||
int missingMethods = msg.arg1;
|
||||
args = (SomeArgs) msg.obj;
|
||||
try {
|
||||
SessionState session = (SessionState)args.arg1;
|
||||
SessionState session = (SessionState) args.arg1;
|
||||
setEnabledSessionInMainThread(session);
|
||||
session.method.startInput((IInputContext)args.arg2,
|
||||
(EditorInfo)args.arg3);
|
||||
session.method.startInput((IInputContext) args.arg2, missingMethods,
|
||||
(EditorInfo) args.arg3);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
args.recycle();
|
||||
return true;
|
||||
case MSG_RESTART_INPUT:
|
||||
args = (SomeArgs)msg.obj;
|
||||
}
|
||||
case MSG_RESTART_INPUT: {
|
||||
int missingMethods = msg.arg1;
|
||||
args = (SomeArgs) msg.obj;
|
||||
try {
|
||||
SessionState session = (SessionState)args.arg1;
|
||||
SessionState session = (SessionState) args.arg1;
|
||||
setEnabledSessionInMainThread(session);
|
||||
session.method.restartInput((IInputContext)args.arg2,
|
||||
(EditorInfo)args.arg3);
|
||||
session.method.restartInput((IInputContext) args.arg2, missingMethods,
|
||||
(EditorInfo) args.arg3);
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
args.recycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
|
||||
@@ -222,7 +222,8 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
|
||||
public InputBindResult startInputOrWindowGainedFocus(
|
||||
/* @InputMethodClient.StartInputReason */ int startInputReason,
|
||||
IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext)
|
||||
int windowFlags, EditorInfo attribute, IInputContext inputContext,
|
||||
/* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags)
|
||||
throws RemoteException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user