diff options
| author | Xin Li <delphij@google.com> | 2018-08-07 16:51:24 +0000 |
|---|---|---|
| committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2018-08-07 16:51:24 +0000 |
| commit | e80b45506501815061b079dcb10bf87443bd385d (patch) | |
| tree | 4d74a37a2b5bab1dfa593dd0b1565cd42b720c16 /core/java/android/inputmethodservice | |
| parent | 38c9e614af1f516f44f2a74fb9d0ec6963f809a8 (diff) | |
| parent | 02857a72198613a0583cdf6863edb2df59beee04 (diff) | |
Merge "Merge Android Pie into master"
Diffstat (limited to 'core/java/android/inputmethodservice')
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(); } |
