summaryrefslogtreecommitdiff
path: root/core/java/android/inputmethodservice
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-07 16:51:24 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2018-08-07 16:51:24 +0000
commite80b45506501815061b079dcb10bf87443bd385d (patch)
tree4d74a37a2b5bab1dfa593dd0b1565cd42b720c16 /core/java/android/inputmethodservice
parent38c9e614af1f516f44f2a74fb9d0ec6963f809a8 (diff)
parent02857a72198613a0583cdf6863edb2df59beee04 (diff)
Merge "Merge Android Pie into master"
Diffstat (limited to 'core/java/android/inputmethodservice')
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java4
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java93
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java386
-rw-r--r--core/java/android/inputmethodservice/Keyboard.java1
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java14
5 files changed, 368 insertions, 130 deletions
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 29177b6b47cf..185215a5ce75 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,6 +16,7 @@
package android.inputmethodservice;
+import android.annotation.MainThread;
import android.annotation.NonNull;
import android.app.Service;
import android.content.Intent;
@@ -62,6 +63,7 @@ public abstract class AbstractInputMethodService extends Service
* back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
* AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
*/
+ @MainThread
public void createSession(SessionCallback callback) {
callback.sessionCreated(onCreateInputMethodSessionInterface());
}
@@ -71,6 +73,7 @@ public abstract class AbstractInputMethodService extends Service
* {@link AbstractInputMethodSessionImpl#revokeSelf()
* AbstractInputMethodSessionImpl.setEnabled()} method.
*/
+ @MainThread
public void setSessionEnabled(InputMethodSession session, boolean enabled) {
((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
}
@@ -80,6 +83,7 @@ public abstract class AbstractInputMethodService extends Service
* {@link AbstractInputMethodSessionImpl#revokeSelf()
* AbstractInputMethodSessionImpl.revokeSelf()} method.
*/
+ @MainThread
public void revokeSession(InputMethodSession session) {
((AbstractInputMethodSessionImpl)session).revokeSelf();
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 765aff96c704..2c7e51a1db25 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -16,14 +16,8 @@
package android.inputmethodservice;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.IInputSessionCallback;
-import com.android.internal.view.InputConnectionWrapper;
-
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -41,11 +35,20 @@ import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InputConnectionWrapper;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -67,17 +70,27 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
-
+
final WeakReference<AbstractInputMethodService> mTarget;
final Context mContext;
final HandlerCaller mCaller;
final WeakReference<InputMethod> mInputMethod;
final int mTargetSdkVersion;
-
- static class Notifier {
- boolean notified;
- }
-
+
+ /**
+ * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
+ * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
+ * called or not, mainly to avoid unnecessary blocking operations.
+ *
+ * <p>This field must be set and cleared only from the binder thread(s), where the system
+ * guarantees that {@link #bindInput(InputBinding)},
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #unbindInput()} are called with the same order as the original calls
+ * in {@link com.android.server.InputMethodManagerService}. See {@link IBinder#FLAG_ONEWAY}
+ * for detailed semantics.</p>
+ */
+ AtomicBoolean mIsUnbindIssued = null;
+
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
@@ -108,20 +121,16 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
}
-
- public IInputMethodWrapper(AbstractInputMethodService context,
- InputMethod inputMethod) {
- mTarget = new WeakReference<AbstractInputMethodService>(context);
+
+ public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
+ mTarget = new WeakReference<>(context);
mContext = context.getApplicationContext();
mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
- mInputMethod = new WeakReference<InputMethod>(inputMethod);
+ mInputMethod = new WeakReference<>(inputMethod);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
}
- public InputMethod getInternalInputMethod() {
- return mInputMethod.get();
- }
-
+ @MainThread
@Override
public void executeMessage(Message msg) {
InputMethod inputMethod = mInputMethod.get();
@@ -169,8 +178,10 @@ class IInputMethodWrapper extends IInputMethod.Stub
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
+ final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
final InputConnection ic = inputContext != null
- ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
+ ? new InputConnectionWrapper(
+ mTarget, inputContext, missingMethods, isUnbindIssued) : null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
startInputToken);
@@ -205,6 +216,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Unhandled message code: " + msg.what);
}
+ @BinderThread
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
AbstractInputMethodService target = mTarget.get();
@@ -232,40 +244,63 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void attachToken(IBinder token) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
+ @BinderThread
@Override
public void bindInput(InputBinding binding) {
+ if (mIsUnbindIssued != null) {
+ Log.e(TAG, "bindInput must be paired with unbindInput.");
+ }
+ mIsUnbindIssued = new AtomicBoolean();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
- IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
+ IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
+ mIsUnbindIssued);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
+ @BinderThread
@Override
public void unbindInput() {
+ if (mIsUnbindIssued != null) {
+ // Signal the flag then forget it.
+ mIsUnbindIssued.set(true);
+ mIsUnbindIssued = null;
+ } else {
+ Log.e(TAG, "unbindInput must be paired with bindInput.");
+ }
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
+ @BinderThread
@Override
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
- missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
+ if (mIsUnbindIssued == null) {
+ Log.e(TAG, "startInput must be called after bindInput.");
+ mIsUnbindIssued = new AtomicBoolean();
+ }
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
+ missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
+ mIsUnbindIssued));
}
+ @BinderThread
@Override
public void createSession(InputChannel channel, IInputSessionCallback callback) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
channel, callback));
}
+ @BinderThread
@Override
public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
try {
@@ -282,6 +317,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void revokeSession(IInputMethodSession session) {
try {
@@ -297,18 +333,21 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @BinderThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
flags, resultReceiver));
}
+ @BinderThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
flags, resultReceiver));
}
+ @BinderThread
@Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index f510b3687bf2..acaaf07984f0 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -18,6 +18,9 @@ package android.inputmethodservice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.CallSuper;
import android.annotation.DrawableRes;
@@ -239,19 +242,89 @@ public class InputMethodService extends AbstractInputMethodService {
static final boolean DEBUG = false;
/**
- * The back button will close the input window.
+ * Allows the system to optimize the back button affordance based on the presence of software
+ * keyboard.
+ *
+ * <p>For instance, on devices that have navigation bar and software-rendered back button, the
+ * system may use a different icon while {@link #isInputViewShown()} returns {@code true}, to
+ * indicate that the back button has "dismiss" affordance.</p>
+ *
+ * <p>Note that {@link KeyEvent#KEYCODE_BACK} events continue to be sent to
+ * {@link #onKeyDown(int, KeyEvent)} even when this mode is specified. The default
+ * implementation of {@link #onKeyDown(int, KeyEvent)} for {@link KeyEvent#KEYCODE_BACK} does
+ * not take this mode into account.</p>
+ *
+ * <p>For API level {@link android.os.Build.VERSION_CODES#O_MR1} and lower devices, this is the
+ * only mode you can safely specify without worrying about the compatibility.</p>
+ *
+ * @see #setBackDisposition(int)
+ */
+ public static final int BACK_DISPOSITION_DEFAULT = 0;
+
+ /**
+ * Deprecated flag.
+ *
+ * <p>To avoid compatibility issues, IME developers should not use this flag.</p>
+ *
+ * @deprecated on {@link android.os.Build.VERSION_CODES#P} and later devices, this flag is
+ * handled as a synonym of {@link #BACK_DISPOSITION_DEFAULT}. On
+ * {@link android.os.Build.VERSION_CODES#O_MR1} and prior devices, expected behavior
+ * of this mode had not been well defined. Most likely the end result would be the
+ * same as {@link #BACK_DISPOSITION_DEFAULT}. Either way it is not recommended to
+ * use this mode
+ * @see #setBackDisposition(int)
+ */
+ @Deprecated
+ public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1;
+
+ /**
+ * Deprecated flag.
+ *
+ * <p>To avoid compatibility issues, IME developers should not use this flag.</p>
+ *
+ * @deprecated on {@link android.os.Build.VERSION_CODES#P} and later devices, this flag is
+ * handled as a synonym of {@link #BACK_DISPOSITION_DEFAULT}. On
+ * {@link android.os.Build.VERSION_CODES#O_MR1} and prior devices, expected behavior
+ * of this mode had not been well defined. In AOSP implementation running on devices
+ * that have navigation bar, specifying this flag could change the software back
+ * button to "Dismiss" icon no matter whether the software keyboard is shown or not,
+ * but there would be no easy way to restore the icon state even after IME lost the
+ * connection to the application. To avoid user confusions, do not specify this mode
+ * anyway
+ * @see #setBackDisposition(int)
*/
- public static final int BACK_DISPOSITION_DEFAULT = 0; // based on window
+ @Deprecated
+ public static final int BACK_DISPOSITION_WILL_DISMISS = 2;
/**
- * This input method will not consume the back key.
+ * Asks the system to not adjust the back button affordance even when the software keyboard is
+ * shown.
+ *
+ * <p>This mode is useful for UI modes where IME's main soft input window is used for some
+ * supplemental UI, such as floating candidate window for languages such as Chinese and
+ * Japanese, where users expect the back button is, or at least looks to be, handled by the
+ * target application rather than the UI shown by the IME even while {@link #isInputViewShown()}
+ * returns {@code true}.</p>
+ *
+ * <p>Note that {@link KeyEvent#KEYCODE_BACK} events continue to be sent to
+ * {@link #onKeyDown(int, KeyEvent)} even when this mode is specified. The default
+ * implementation of {@link #onKeyDown(int, KeyEvent)} for {@link KeyEvent#KEYCODE_BACK} does
+ * not take this mode into account.</p>
+ *
+ * @see #setBackDisposition(int)
*/
- public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1; // back
+ public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3;
/**
- * This input method will consume the back key.
+ * Enum flag to be used for {@link #setBackDisposition(int)}.
+ *
+ * @hide
*/
- public static final int BACK_DISPOSITION_WILL_DISMISS = 2; // down
+ @Retention(SOURCE)
+ @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS,
+ BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING},
+ prefix = "BACK_DISPOSITION_")
+ public @interface BackDispositionMode {}
/**
* @hide
@@ -265,6 +338,10 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public static final int IME_VISIBLE = 0x2;
+ // Min and max values for back disposition.
+ private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT;
+ private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
+
InputMethodManager mImm;
@UnsupportedAppUsage
@@ -331,6 +408,8 @@ public class InputMethodService extends AbstractInputMethodService {
boolean mIsInputViewShown;
int mStatusIcon;
+
+ @BackDispositionMode
int mBackDisposition;
/**
@@ -345,42 +424,35 @@ public class InputMethodService extends AbstractInputMethodService {
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
- final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
- new ViewTreeObserver.OnComputeInternalInsetsListener() {
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (isExtractViewShown()) {
- // In true fullscreen mode, we just say the window isn't covering
- // any content so we don't impact whatever is behind.
- View decor = getWindow().getWindow().getDecorView();
- info.contentInsets.top = info.visibleInsets.top
- = decor.getHeight();
- info.touchableRegion.setEmpty();
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- } else {
- onComputeInsets(mTmpInsets);
- info.contentInsets.top = mTmpInsets.contentTopInsets;
- info.visibleInsets.top = mTmpInsets.visibleTopInsets;
- info.touchableRegion.set(mTmpInsets.touchableRegion);
- info.setTouchableInsets(mTmpInsets.touchableInsets);
- }
+ final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+ if (isExtractViewShown()) {
+ // In true fullscreen mode, we just say the window isn't covering
+ // any content so we don't impact whatever is behind.
+ View decor = getWindow().getWindow().getDecorView();
+ info.contentInsets.top = info.visibleInsets.top = decor.getHeight();
+ info.touchableRegion.setEmpty();
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ } else {
+ onComputeInsets(mTmpInsets);
+ info.contentInsets.top = mTmpInsets.contentTopInsets;
+ info.visibleInsets.top = mTmpInsets.visibleTopInsets;
+ info.touchableRegion.set(mTmpInsets.touchableRegion);
+ info.setTouchableInsets(mTmpInsets.touchableInsets);
}
};
- final View.OnClickListener mActionClickListener = new View.OnClickListener() {
- public void onClick(View v) {
- final EditorInfo ei = getCurrentInputEditorInfo();
- final InputConnection ic = getCurrentInputConnection();
- if (ei != null && ic != null) {
- if (ei.actionId != 0) {
- ic.performEditorAction(ei.actionId);
- } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
- != EditorInfo.IME_ACTION_NONE) {
- ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
- }
+ final View.OnClickListener mActionClickListener = v -> {
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ei != null && ic != null) {
+ if (ei.actionId != 0) {
+ ic.performEditorAction(ei.actionId);
+ } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE) {
+ ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION);
}
}
};
-
+
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
@@ -388,20 +460,24 @@ public class InputMethodService extends AbstractInputMethodService {
*/
public class InputMethodImpl extends AbstractInputMethodImpl {
/**
- * Take care of attaching the given window token provided by the system.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void attachToken(IBinder token) {
if (mToken == null) {
mToken = token;
mWindow.setToken(token);
}
}
-
+
/**
- * Handle a new input binding, calling
- * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
- * when done.
+ * {@inheritDoc}
+ *
+ * <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
*/
+ @MainThread
+ @Override
public void bindInput(InputBinding binding) {
mInputBinding = binding;
mInputConnection = binding.getConnection();
@@ -415,8 +491,12 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Clear the current input binding.
+ * {@inheritDoc}
+ *
+ * <p>Calls {@link InputMethodService#onUnbindInput()} when done.</p>
*/
+ @MainThread
+ @Override
public void unbindInput() {
if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
+ " ic=" + mInputConnection);
@@ -425,11 +505,21 @@ public class InputMethodService extends AbstractInputMethodService {
mInputConnection = null;
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void restartInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
doStartInput(ic, attribute, true);
@@ -439,6 +529,7 @@ public class InputMethodService extends AbstractInputMethodService {
* {@inheritDoc}
* @hide
*/
+ @MainThread
@Override
public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
@NonNull EditorInfo editorInfo, boolean restarting,
@@ -453,8 +544,10 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Handle a request by the system to hide the soft input area.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "hideSoftInput()");
boolean wasVis = isInputViewShown();
@@ -471,8 +564,10 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Handle a request by the system to show the soft input area.
+ * {@inheritDoc}
*/
+ @MainThread
+ @Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
if (DEBUG) Log.v(TAG, "showSoftInput()");
boolean wasVis = isInputViewShown();
@@ -490,9 +585,8 @@ public class InputMethodService extends AbstractInputMethodService {
}
clearInsetOfPreviousIme();
// If user uses hard keyboard, IME button should always be shown.
- boolean showing = isInputViewShown();
mImm.setImeWindowStatus(mToken, mStartInputToken,
- IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
+ mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
if (resultReceiver != null) {
resultReceiver.send(wasVis != isInputViewShown()
? InputMethodManager.RESULT_SHOWN
@@ -501,6 +595,11 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @MainThread
+ @Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
onCurrentInputMethodSubtypeChanged(subtype);
}
@@ -832,6 +931,11 @@ public class InputMethodService extends AbstractInputMethodService {
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
+ // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set
+ // by default (but IME developers can opt this out later if they want a new behavior).
+ mWindow.getWindow().setFlags(
+ FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
@@ -862,8 +966,6 @@ public class InputMethodService extends AbstractInputMethodService {
mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService);
mRootView = mInflater.inflate(
com.android.internal.R.layout.input_method, null);
- mRootView.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mWindow.setContentView(mRootView);
mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsComputer);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
@@ -872,20 +974,20 @@ public class InputMethodService extends AbstractInputMethodService {
mWindow.getWindow().setWindowAnimations(
com.android.internal.R.style.Animation_InputMethodFancy);
}
- mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
+ mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
mExtractViewHidden = false;
- mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
+ mExtractFrame = mRootView.findViewById(android.R.id.extractArea);
mExtractView = null;
mExtractEditText = null;
mExtractAccessories = null;
mExtractAction = null;
mFullscreenApplied = false;
-
- mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
- mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
+
+ mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea);
+ mInputFrame = mRootView.findViewById(android.R.id.inputArea);
mInputView = null;
mIsInputViewShown = false;
-
+
mExtractFrame.setVisibility(View.GONE);
mCandidatesVisibility = getCandidatesHiddenVisibility();
mCandidatesFrame.setVisibility(mCandidatesVisibility);
@@ -995,11 +1097,38 @@ public class InputMethodService extends AbstractInputMethodService {
public Dialog getWindow() {
return mWindow;
}
-
- public void setBackDisposition(int disposition) {
+
+ /**
+ * Sets the disposition mode that indicates the expected affordance for the back button.
+ *
+ * <p>Keep in mind that specifying this flag does not change the the default behavior of
+ * {@link #onKeyDown(int, KeyEvent)}. It is IME developers' responsibility for making sure that
+ * their custom implementation of {@link #onKeyDown(int, KeyEvent)} is consistent with the mode
+ * specified to this API.</p>
+ *
+ * @see #getBackDisposition()
+ * @param disposition disposition mode to be set
+ */
+ public void setBackDisposition(@BackDispositionMode int disposition) {
+ if (disposition == mBackDisposition) {
+ return;
+ }
+ if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) {
+ Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified.");
+ return;
+ }
mBackDisposition = disposition;
+ mImm.setImeWindowStatus(mToken, mStartInputToken, mapToImeWindowStatus(isInputViewShown()),
+ mBackDisposition);
}
+ /**
+ * Retrieves the current disposition mode that indicates the expected back button affordance.
+ *
+ * @see #setBackDisposition(int)
+ * @return currently selected disposition mode
+ */
+ @BackDispositionMode
public int getBackDisposition() {
return mBackDisposition;
}
@@ -1044,7 +1173,43 @@ public class InputMethodService extends AbstractInputMethodService {
}
return mInputConnection;
}
-
+
+ /**
+ * Force switch to the last used input method and subtype. If the last input method didn't have
+ * any subtypes, the framework will simply switch to the last input method with no subtype
+ * specified.
+ * @return true if the current input method and subtype was successfully switched to the last
+ * used input method and subtype.
+ */
+ public final boolean switchToPreviousInputMethod() {
+ return mImm.switchToPreviousInputMethodInternal(mToken);
+ }
+
+ /**
+ * Force switch to the next input method and subtype. If there is no IME enabled except
+ * current IME and subtype, do nothing.
+ * @param onlyCurrentIme if true, the framework will find the next subtype which
+ * belongs to the current IME
+ * @return true if the current input method and subtype was successfully switched to the next
+ * input method and subtype.
+ */
+ public final boolean switchToNextInputMethod(boolean onlyCurrentIme) {
+ return mImm.switchToNextInputMethodInternal(mToken, onlyCurrentIme);
+ }
+
+ /**
+ * Returns true if the current IME needs to offer the users ways to switch to a next input
+ * method (e.g. a globe key.).
+ * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
+ * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
+ * <p> Note that the system determines the most appropriate next input method
+ * and subtype in order to provide the consistent user experience in switching
+ * between IMEs and subtypes.
+ */
+ public final boolean shouldOfferSwitchingToNextInputMethod() {
+ return mImm.shouldOfferSwitchingToNextInputMethodInternal(mToken);
+ }
+
public boolean getCurrentInputStarted() {
return mInputStarted;
}
@@ -1355,28 +1520,40 @@ public class InputMethodService extends AbstractInputMethodService {
public int getCandidatesHiddenVisibility() {
return isExtractViewShown() ? View.GONE : View.INVISIBLE;
}
-
+
public void showStatusIcon(@DrawableRes int iconResId) {
mStatusIcon = iconResId;
- mImm.showStatusIcon(mToken, getPackageName(), iconResId);
+ mImm.showStatusIconInternal(mToken, getPackageName(), iconResId);
}
-
+
public void hideStatusIcon() {
mStatusIcon = 0;
- mImm.hideStatusIcon(mToken);
+ mImm.hideStatusIconInternal(mToken);
}
-
+
/**
* Force switch to a new input method, as identified by <var>id</var>. This
* input method will be destroyed, and the requested one started on the
* current input field.
*
- * @param id Unique identifier of the new input method ot start.
+ * @param id Unique identifier of the new input method to start.
*/
public void switchInputMethod(String id) {
- mImm.setInputMethod(mToken, id);
+ mImm.setInputMethodInternal(mToken, id);
}
-
+
+ /**
+ * Force switch to a new input method, as identified by {@code id}. This
+ * input method will be destroyed, and the requested one started on the
+ * current input field.
+ *
+ * @param id Unique identifier of the new input method to start.
+ * @param subtype The new subtype of the new input method to be switched to.
+ */
+ public final void switchInputMethod(String id, InputMethodSubtype subtype) {
+ mImm.setInputMethodAndSubtypeInternal(mToken, id, subtype);
+ }
+
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
mExtractFrame.addView(view, new FrameLayout.LayoutParams(
@@ -1384,13 +1561,13 @@ public class InputMethodService extends AbstractInputMethodService {
ViewGroup.LayoutParams.MATCH_PARENT));
mExtractView = view;
if (view != null) {
- mExtractEditText = (ExtractEditText)view.findViewById(
+ mExtractEditText = view.findViewById(
com.android.internal.R.id.inputExtractEditText);
mExtractEditText.setIME(this);
mExtractAction = view.findViewById(
com.android.internal.R.id.inputExtractAction);
if (mExtractAction != null) {
- mExtractAccessories = (ViewGroup)view.findViewById(
+ mExtractAccessories = view.findViewById(
com.android.internal.R.id.inputExtractAccessories);
}
startExtractingText(false);
@@ -1639,7 +1816,7 @@ public class InputMethodService extends AbstractInputMethodService {
// Rethrow the exception to preserve the existing behavior. Some IMEs may have directly
// called this method and relied on this exception for some clean-up tasks.
// TODO: Give developers a clear guideline of whether it's OK to call this method or
- // InputMethodManager#showSoftInputFromInputMethod() should always be used instead.
+ // InputMethodService#requestShowSelf(int) should always be used instead.
throw e;
} finally {
// TODO: Is it OK to set true when we get BadTokenException?
@@ -1690,7 +1867,7 @@ public class InputMethodService extends AbstractInputMethodService {
startExtractingText(false);
}
- final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0);
+ final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());
if (previousImeWindowStatus != nextImeWindowStatus) {
mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,
mBackDisposition);
@@ -1961,27 +2138,30 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Close this input method's soft input area, removing it from the display.
- * The input method will continue running, but the user can no longer use
- * it to generate input by touching the screen.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
- * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
+ *
+ * The input method will continue running, but the user can no longer use it to generate input
+ * by touching the screen.
+ *
+ * @see InputMethodManager#HIDE_IMPLICIT_ONLY
+ * @see InputMethodManager#HIDE_NOT_ALWAYS
+ * @param flags Provides additional operating flags.
*/
public void requestHideSelf(int flags) {
- mImm.hideSoftInputFromInputMethod(mToken, flags);
+ mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
}
-
+
/**
- * Show the input method. This is a call back to the
- * IMF to handle showing the input method.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#SHOW_FORCED
- * InputMethodManager.} bit set.
+ * Show the input method's soft input area, so the user sees the input method window and can
+ * interact with it.
+ *
+ * @see InputMethodManager#SHOW_IMPLICIT
+ * @see InputMethodManager#SHOW_FORCED
+ * @param flags Provides additional operating flags.
*/
- private void requestShowSelf(int flags) {
- mImm.showSoftInputFromInputMethod(mToken, flags);
+ public final void requestShowSelf(int flags) {
+ mImm.showSoftInputFromInputMethodInternal(mToken, flags);
}
-
+
private boolean handleBack(boolean doIt) {
if (mShowInputRequested) {
// If the soft input area is shown, back closes it and we
@@ -2015,18 +2195,28 @@ public class InputMethodService extends AbstractInputMethodService {
return mExtractEditText;
}
+
/**
- * Override this to intercept key down events before they are processed by the
- * application. If you return true, the application will not
- * process the event itself. If you return false, the normal application processing
- * will occur as if the IME had not seen the event at all.
- *
- * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
- * KeyEvent.KEYCODE_BACK} if the IME is currently shown, to
- * possibly hide it when the key goes up (if not canceled or long pressed). In
- * addition, in fullscreen mode only, it will consume DPAD movement
- * events to move the cursor in the extracted text view, not allowing
- * them to perform navigation in the underlying application.
+ * Called back when a {@link KeyEvent} is forwarded from the target application.
+ *
+ * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK} if the IME is
+ * currently shown , to possibly hide it when the key goes up (if not canceled or long pressed).
+ * In addition, in fullscreen mode only, it will consume DPAD movement events to move the cursor
+ * in the extracted text view, not allowing them to perform navigation in the underlying
+ * application.</p>
+ *
+ * <p>The default implementation does not take flags specified to
+ * {@link #setBackDisposition(int)} into account, even on API version
+ * {@link android.os.Build.VERSION_CODES#P} and later devices. IME developers are responsible
+ * for making sure that their special handling for {@link KeyEvent#KEYCODE_BACK} are consistent
+ * with the flag they specified to {@link #setBackDisposition(int)}.</p>
+ *
+ * @param keyCode The value in {@code event.getKeyCode()}
+ * @param event Description of the key event
+ *
+ * @return {@code true} if the event is consumed by the IME and the application no longer needs
+ * to consume it. Return {@code false} when the event should be handled as if the IME
+ * had not seen the event at all.
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
@@ -2651,7 +2841,7 @@ public class InputMethodService extends AbstractInputMethodService {
* application.
* This cannot be {@code null}.
* @param inputConnection {@link InputConnection} with which
- * {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called.
+ * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} will be called.
* @hide
*/
@Override
@@ -2666,6 +2856,10 @@ public class InputMethodService extends AbstractInputMethodService {
mImm.exposeContent(mToken, inputContentInfo, getCurrentInputEditorInfo());
}
+ private static int mapToImeWindowStatus(boolean isInputViewShown) {
+ return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0);
+ }
+
/**
* Performs a dump of the InputMethodService's internal state. Override
* to add your own information to the dump.
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index f063496beb80..ec5f05067120 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -627,6 +627,7 @@ public class Keyboard {
rows.add(row);
}
+ @UnsupportedAppUsage
final void resize(int newWidth, int newHeight) {
int numRows = rows.size();
for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index befc2bb1de28..9ca804975b7a 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -22,18 +22,15 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
-import android.graphics.Paint.Align;
-import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -62,6 +59,7 @@ import java.util.Map;
* @attr ref android.R.styleable#KeyboardView_keyBackground
* @attr ref android.R.styleable#KeyboardView_keyPreviewLayout
* @attr ref android.R.styleable#KeyboardView_keyPreviewOffset
+ * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight
* @attr ref android.R.styleable#KeyboardView_labelTextSize
* @attr ref android.R.styleable#KeyboardView_keyTextSize
* @attr ref android.R.styleable#KeyboardView_keyTextColor
@@ -665,11 +663,13 @@ public class KeyboardView extends View implements View.OnClickListener {
invalidateAllKeys();
mKeyboardChanged = false;
}
- final Canvas canvas = mCanvas;
- canvas.clipRect(mDirtyRect, Op.REPLACE);
if (mKeyboard == null) return;
+ mCanvas.save();
+ final Canvas canvas = mCanvas;
+ canvas.clipRect(mDirtyRect);
+
final Paint paint = mPaint;
final Drawable keyBackground = mKeyBackground;
final Rect clipRegion = mClipRegion;
@@ -761,7 +761,7 @@ public class KeyboardView extends View implements View.OnClickListener {
paint.setColor(0xFF00FF00);
canvas.drawCircle((mStartX + mLastX) / 2, (mStartY + mLastY) / 2, 2, paint);
}
-
+ mCanvas.restore();
mDrawPending = false;
mDirtyRect.setEmpty();
}