diff options
| author | 2019-01-25 11:47:57 -0800 | |
|---|---|---|
| committer | 2019-01-28 16:58:32 +0100 | |
| commit | 2cbcd7ffbf689591a234bee7c416ecf16bd1e684 (patch) | |
| tree | cfc36075960b55ecd71eef4b03ff9ee2a24fb2e9 | |
| parent | 5cccc2bd8eae729c60c680fe397ed9472496e23c (diff) | |
Pipe IME state into insets (IME transitions 3/n)
Add a IME state changes callback that pipes IME state into the Inset
consumer.
Bug: 118599175
Bug: 118118435
Test: atest InsetControllerTest
Test: atest InsetSourceConsumerTest
Test: atest ImeInsetsSourceConsumerTest
Change-Id: Id878226418e19cdf0499a0094f1d5c47fea33125
11 files changed, 552 insertions, 5 deletions
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 5b3ad77dbf43..333cfbd400dd 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -597,6 +597,7 @@ public class InputMethodService extends AbstractInputMethodService { Log.v(TAG, "Making IME window invisible"); } setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + applyVisibilityInInsetsConsumer(false /* setVisible */); onPreRenderedWindowVisibilityChanged(false /* setVisible */); } else { mShowInputFlags = 0; @@ -625,10 +626,10 @@ public class InputMethodService extends AbstractInputMethodService { ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { if (mIsPreRendered) { - // TODO: notify visibility to insets consumer. if (DEBUG) { Log.v(TAG, "Making IME window visible"); } + applyVisibilityInInsetsConsumer(true /* setVisible */); onPreRenderedWindowVisibilityChanged(true /* setVisible */); } else { showWindow(true); @@ -1887,10 +1888,23 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); } + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } + /** + * Notify {@link android.view.ImeInsetsSourceConsumer} if IME has been pre-rendered + * for current EditorInfo, when pre-rendering is enabled. + */ + private void maybeNotifyPreRendered() { + if (!mCanPreRender || !mIsPreRendered) { + return; + } + mPrivOps.reportPreRendered(getCurrentInputEditorInfo()); + } + + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; mDecorViewVisible = true; @@ -1942,6 +1956,18 @@ public class InputMethodService extends AbstractInputMethodService { } } + /** + * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when + * pre-rendering is enabled. + * @param setVisible {@code true} to make it visible, false to hide it. + */ + private void applyVisibilityInInsetsConsumer(boolean setVisible) { + if (!mIsPreRendered) { + return; + } + mPrivOps.applyImeVisibility(setVisible); + } + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); @@ -2081,6 +2107,7 @@ public class InputMethodService extends AbstractInputMethodService { // When IME is not pre-rendered, this will actually show the IME. if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } else { diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java new file mode 100644 index 000000000000..7026d2b1389c --- /dev/null +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019 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; + +import static android.view.InsetsState.TYPE_IME; + +import android.os.Parcel; +import android.text.TextUtils; +import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets.Type; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.function.Supplier; + +/** + * Controls the visibility and animations of IME window insets source. + * @hide + */ +public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { + private EditorInfo mFocusedEditor; + private EditorInfo mPreRenderedEditor; + /** + * Determines if IME would be shown next time IME is pre-rendered for currently focused + * editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}. + */ + private boolean mShowOnNextImeRender; + private boolean mHasWindowFocus; + + public ImeInsetsSourceConsumer( + InsetsState state, Supplier<Transaction> transactionSupplier, + InsetsController controller) { + super(TYPE_IME, state, transactionSupplier, controller); + } + + public void onPreRendered(EditorInfo info) { + mPreRenderedEditor = info; + if (mShowOnNextImeRender) { + mShowOnNextImeRender = false; + if (isServedEditorRendered()) { + applyImeVisibility(true /* setVisible */); + } + } + } + + public void onServedEditorChanged(EditorInfo info) { + if (isDummyOrEmptyEditor(info)) { + mShowOnNextImeRender = false; + } + mFocusedEditor = info; + } + + public void applyImeVisibility(boolean setVisible) { + if (!mHasWindowFocus) { + // App window doesn't have focus, any visibility changes would be no-op. + return; + } + + if (setVisible) { + mController.show(Type.IME); + } else { + mController.hide(Type.IME); + } + } + + @Override + public void onWindowFocusGained() { + mHasWindowFocus = true; + getImm().registerImeConsumer(this); + } + + @Override + public void onWindowFocusLost() { + mHasWindowFocus = false; + } + + private boolean isDummyOrEmptyEditor(EditorInfo info) { + // TODO(b/123044812): Handle dummy input gracefully in IME Insets API + return info == null || (info.fieldId <= 0 && info.inputType <= 0); + } + + private boolean isServedEditorRendered() { + if (mFocusedEditor == null || mPreRenderedEditor == null + || isDummyOrEmptyEditor(mFocusedEditor) + || isDummyOrEmptyEditor(mPreRenderedEditor)) { + // No view is focused or ready. + return false; + } + return areEditorsSimilar(mFocusedEditor, mPreRenderedEditor); + } + + @VisibleForTesting + public static boolean areEditorsSimilar(EditorInfo info1, EditorInfo info2) { + // We don't need to compare EditorInfo.fieldId (View#id) since that shouldn't change + // IME views. + boolean areOptionsSimilar = + info1.imeOptions == info2.imeOptions + && info1.inputType == info2.inputType + && TextUtils.equals(info1.packageName, info2.packageName); + areOptionsSimilar &= info1.privateImeOptions != null + ? info1.privateImeOptions.equals(info2.privateImeOptions) : true; + + if (!areOptionsSimilar) { + return false; + } + + // compare bundle extras. + if ((info1.extras == null && info2.extras == null) || info1.extras == info2.extras) { + return true; + } + if ((info1.extras == null && info2.extras != null) + || (info1.extras == null && info2.extras != null)) { + return false; + } + if (info1.extras.hashCode() == info2.extras.hashCode() + || info1.extras.equals(info1)) { + return true; + } + if (info1.extras.size() != info2.extras.size()) { + return false; + } + if (info1.extras.toString().equals(info2.extras.toString())) { + return true; + } + + // Compare bytes + Parcel parcel1 = Parcel.obtain(); + info1.extras.writeToParcel(parcel1, 0); + parcel1.setDataPosition(0); + Parcel parcel2 = Parcel.obtain(); + info2.extras.writeToParcel(parcel2, 0); + parcel2.setDataPosition(0); + + return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); + } + + private InputMethodManager getImm() { + return mController.getViewRoot().mDisplayContext.getSystemService(InputMethodManager.class); + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 2142c36f8803..4f9ecd575326 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsState.TYPE_IME; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; @@ -262,7 +264,7 @@ public class InsetsController implements WindowInsetsController { if (controller != null) { return controller; } - controller = new InsetsSourceConsumer(type, mState, Transaction::new, this); + controller = createConsumerOfType(type); mSourceConsumers.put(type, controller); return controller; } @@ -274,6 +276,32 @@ public class InsetsController implements WindowInsetsController { } /** + * Called when current window gains focus. + */ + public void onWindowFocusGained() { + getSourceConsumer(TYPE_IME).onWindowFocusGained(); + } + + /** + * Called when current window loses focus. + */ + public void onWindowFocusLost() { + getSourceConsumer(TYPE_IME).onWindowFocusLost(); + } + + ViewRootImpl getViewRoot() { + return mViewRoot; + } + + private InsetsSourceConsumer createConsumerOfType(int type) { + if (type == TYPE_IME) { + return new ImeInsetsSourceConsumer(mState, Transaction::new, this); + } else { + return new InsetsSourceConsumer(type, mState, Transaction::new, this); + } + } + + /** * Sends the local visibility state back to window manager. */ private void sendStateToWindowManager() { diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 7937cb69b80e..7c776f82211e 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -30,12 +30,12 @@ import java.util.function.Supplier; */ public class InsetsSourceConsumer { + protected final InsetsController mController; + protected boolean mVisible; private final Supplier<Transaction> mTransactionSupplier; private final @InternalInsetType int mType; private final InsetsState mState; - private final InsetsController mController; private @Nullable InsetsSourceControl mSourceControl; - private boolean mVisible; public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state, Supplier<Transaction> transactionSupplier, InsetsController controller) { @@ -76,6 +76,16 @@ public class InsetsSourceConsumer { setVisible(false); } + /** + * Called when current window gains focus + */ + public void onWindowFocusGained() {} + + /** + * Called when current window loses focus. + */ + public void onWindowFocusLost() {} + boolean applyLocalVisibilityOverride() { // If we don't have control, we are not able to change the visibility. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index a031b704627f..f47eb10efd29 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2802,6 +2802,11 @@ public final class ViewRootImpl implements ViewParent, hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } + if (hasWindowFocus) { + mInsetsController.onWindowFocusGained(); + } else { + mInsetsController.onWindowFocusLost(); + } if (mAdded) { profileRendering(hasWindowFocus); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0cb1800996c9..7fee3ef29a09 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -55,6 +55,7 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; import android.view.Display; +import android.view.ImeInsetsSourceConsumer; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; @@ -441,6 +442,13 @@ public final class InputMethodManager { */ private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE; + /** + * When {@link ViewRootImpl#sNewInsetsMode} is set to + * >= {@link ViewRootImpl#NEW_INSETS_MODE_IME}, {@link ImeInsetsSourceConsumer} applies the + * IME visibility and listens for other state changes. + */ + private ImeInsetsSourceConsumer mImeInsetsConsumer; + final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20); final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20); @@ -454,6 +462,8 @@ public final class InputMethodManager { static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; static final int MSG_REPORT_FULLSCREEN_MODE = 10; + static final int MSG_REPORT_PRE_RENDERED = 15; + static final int MSG_APPLY_IME_VISIBILITY = 20; private static boolean isAutofillUIShowing(View servedView) { AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class); @@ -650,6 +660,23 @@ public final class InputMethodManager { } return; } + case MSG_REPORT_PRE_RENDERED: { + synchronized (mH) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.onPreRendered((EditorInfo) msg.obj); + } + } + return; + + } + case MSG_APPLY_IME_VISIBILITY: { + synchronized (mH) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.applyImeVisibility(msg.arg1 != 0); + } + } + return; + } } } } @@ -729,6 +756,18 @@ public final class InputMethodManager { .sendToTarget(); } + @Override + public void reportPreRendered(EditorInfo info) { + mH.obtainMessage(MSG_REPORT_PRE_RENDERED, 0, 0, info) + .sendToTarget(); + } + + @Override + public void applyImeVisibility(boolean setVisible) { + mH.obtainMessage(MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, 0) + .sendToTarget(); + } + }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); @@ -1515,6 +1554,7 @@ public final class InputMethodManager { // Hook 'em up and let 'er rip. mCurrentTextBoxAttribute = tba; + maybeCallServedViewChangedLocked(tba); mServedConnecting = false; if (mServedInputConnectionWrapper != null) { mServedInputConnectionWrapper.deactivate(); @@ -1730,6 +1770,10 @@ public final class InputMethodManager { mCurrentTextBoxAttribute = null; mCompletions = null; mServedConnecting = true; + // servedView has changed and it's not editable. + if (!mServedView.onCheckIsTextEditor()) { + maybeCallServedViewChangedLocked(null); + } } if (ic != null) { @@ -1828,6 +1872,21 @@ public final class InputMethodManager { } /** + * Register for IME state callbacks and applying visibility in + * {@link android.view.ImeInsetsSourceConsumer}. + * @hide + */ + public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) { + if (imeInsetsConsumer == null) { + throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null."); + } + + synchronized (mH) { + mImeInsetsConsumer = imeInsetsConsumer; + } + } + + /** * Report the current selection range. * * <p><strong>Editor authors</strong>, you need to call this method whenever @@ -2705,6 +2764,12 @@ public final class InputMethodManager { } } + private void maybeCallServedViewChangedLocked(EditorInfo tba) { + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.onServedEditorChanged(tba); + } + } + void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index d0272e08b53a..e27ff0076054 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2019 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. @@ -17,6 +17,7 @@ package com.android.internal.inputmethod; import android.net.Uri; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.inputmethod.IInputContentUriToken; @@ -39,4 +40,6 @@ interface IInputMethodPrivilegedOperations { boolean switchToNextInputMethod(boolean onlyCurrentIme); boolean shouldOfferSwitchingToNextInputMethod(); void notifyUserAction(); + void reportPreRendered(in EditorInfo info); + void applyImeVisibility(boolean setVisible); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 8978496073e5..d42c607b98bf 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; @@ -347,4 +348,40 @@ public final class InputMethodPrivilegedOperations { throw e.rethrowFromSystemServer(); } } + + /** + * Calls {@link IInputMethodPrivilegedOperations#reportPreRendered(info)}. + * + * @param info {@link EditorInfo} of the currently rendered {@link TextView}. + */ + @AnyThread + public void reportPreRendered(EditorInfo info) { + final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); + if (ops == null) { + return; + } + try { + ops.reportPreRendered(info); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}. + * + * @param setVisible {@code true} to set IME visible, else hidden. + */ + @AnyThread + public void applyImeVisibility(boolean setVisible) { + final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); + if (ops == null) { + return; + } + try { + ops.applyImeVisibility(setVisible); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 17b2bc46de36..2cfdaaa5ada6 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -16,6 +16,8 @@ package com.android.internal.view; +import android.view.inputmethod.EditorInfo; + import com.android.internal.view.InputBindResult; /** @@ -27,4 +29,6 @@ oneway interface IInputMethodClient { void onUnbindMethod(int sequence, int unbindReason); void setActive(boolean active, boolean fullscreen); void reportFullscreenMode(boolean fullscreen); + void reportPreRendered(in EditorInfo info); + void applyImeVisibility(boolean setVisible); } diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java new file mode 100644 index 000000000000..b07cb99f35c5 --- /dev/null +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2018 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; + +import static android.view.ImeInsetsSourceConsumer.areEditorsSimilar; +import static android.view.InsetsState.TYPE_IME; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.content.Context; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Bundle; +import android.platform.test.annotations.Presubmit; +import android.view.SurfaceControl.Transaction; +import android.view.WindowManager.BadTokenException; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.TextView; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.FlakyTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@Presubmit +@FlakyTest(detail = "Promote once confirmed non-flaky") +@RunWith(AndroidJUnit4.class) +public class ImeInsetsSourceConsumerTest { + + Context mContext = InstrumentationRegistry.getTargetContext(); + ImeInsetsSourceConsumer mImeConsumer; + InsetsController mController; + SurfaceControl mLeash; + + @Before + public void setup() { + mLeash = new SurfaceControl.Builder(new SurfaceSession()) + .setName("testSurface") + .build(); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + ViewRootImpl viewRootImpl = new ViewRootImpl(mContext, mContext.getDisplay()); + try { + viewRootImpl.setView(new TextView(mContext), new LayoutParams(), null); + } catch (BadTokenException e) { + // activity isn't running, we will ignore BadTokenException. + } + mController = new InsetsController(viewRootImpl); + final Rect rect = new Rect(5, 5, 5, 5); + mController.calculateInsets( + false, + false, + new DisplayCutout( + Insets.of(10, 10, 10, 10), rect, rect, rect, rect), + rect, rect); + mImeConsumer = new ImeInsetsSourceConsumer( + new InsetsState(), Transaction::new, mController); + }); + } + + @Test + public void testImeVisibility() { + final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash); + mController.onControlsChanged(new InsetsSourceControl[] { ime }); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // test if setVisibility can show IME + mImeConsumer.onWindowFocusGained(); + mImeConsumer.applyImeVisibility(true); + mController.cancelExistingAnimation(); + assertTrue(mController.getSourceConsumer(ime.getType()).isVisible()); + + // test if setVisibility can hide IME + mImeConsumer.applyImeVisibility(false); + mController.cancelExistingAnimation(); + assertFalse(mController.getSourceConsumer(ime.getType()).isVisible()); + }); + } + + @Test + public void testAreEditorsSimilar() { + EditorInfo info1 = new EditorInfo(); + info1.privateImeOptions = "dummy"; + EditorInfo info2 = new EditorInfo(); + + assertFalse(areEditorsSimilar(info1, info2)); + + info1.privateImeOptions = null; + assertTrue(areEditorsSimilar(info1, info2)); + + info1.inputType = info2.inputType = 3; + info1.imeOptions = info2.imeOptions = 0x4; + info1.packageName = info2.packageName = "dummy.package"; + assertTrue(areEditorsSimilar(info1, info2)); + + Bundle extras1 = new Bundle(); + extras1.putByteArray("key1", "value1".getBytes()); + extras1.putChar("key2", 'c'); + Bundle extras2 = new Bundle(); + extras2.putByteArray("key1", "value1".getBytes()); + extras2.putChar("key2", 'c'); + info1.extras = extras1; + info2.extras = extras2; + assertTrue(areEditorsSimilar(info1, info2)); + + Bundle extraBundle = new Bundle(); + ArrayList<Integer> list = new ArrayList<>(); + list.add(2); + list.add(5); + extraBundle.putByteArray("key1", "value1".getBytes()); + extraBundle.putChar("key2", 'c'); + extraBundle.putIntegerArrayList("key3", list); + + extras1.putAll(extraBundle); + extras2.putAll(extraBundle); + assertTrue(areEditorsSimilar(info1, info2)); + + extras2.putChar("key2", 'd'); + assertFalse(areEditorsSimilar(info1, info2)); + + extras2.putChar("key2", 'c'); + extras2.putInt("key4", 1); + assertFalse(areEditorsSimilar(info1, info2)); + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4db541c29448..99bed732446c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -207,6 +207,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static final int MSG_SET_ACTIVE = 3020; static final int MSG_SET_INTERACTIVE = 3030; static final int MSG_REPORT_FULLSCREEN_MODE = 3045; + static final int MSG_REPORT_PRE_RENDERED = 3060; + static final int MSG_APPLY_IME_VISIBILITY = 3070; static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; @@ -3327,6 +3329,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + @BinderThread + private void reportPreRendered(IBinder token, EditorInfo info) { + synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } + if (mCurClient != null && mCurClient.client != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( + MSG_REPORT_PRE_RENDERED, info, mCurClient)); + } + } + } + + @BinderThread + private void applyImeVisibility(IBinder token, boolean setVisible) { + synchronized (mMethodMap) { + if (!calledWithValidTokenLocked(token)) { + return; + } + if (mCurClient != null && mCurClient.client != null) { + executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( + MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient)); + } + } + } + private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { if (token == null) { if (mContext.checkCallingOrSelfPermission( @@ -3580,6 +3608,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } + case MSG_REPORT_PRE_RENDERED: { + args = (SomeArgs) msg.obj; + final EditorInfo info = (EditorInfo) args.arg1; + final ClientState clientState = (ClientState) args.arg2; + try { + clientState.client.reportPreRendered(info); + } catch (RemoteException e) { + Slog.w(TAG, "Got RemoteException sending " + + "reportPreRendered(" + info + ") notification to pid=" + + clientState.pid + " uid=" + clientState.uid); + } + args.recycle(); + return true; + } + case MSG_APPLY_IME_VISIBILITY: { + final boolean setVisible = msg.arg1 != 0; + final ClientState clientState = (ClientState) msg.obj; + try { + clientState.client.applyImeVisibility(setVisible); + } catch (RemoteException e) { + Slog.w(TAG, "Got RemoteException sending " + + "applyImeVisibility(" + setVisible + ") notification to pid=" + + clientState.pid + " uid=" + clientState.uid); + } + return true; + } // -------------------------------------------------------------- case MSG_HARD_KEYBOARD_SWITCH_CHANGED: @@ -4756,5 +4810,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void notifyUserAction() { mImms.notifyUserAction(mToken); } + + @BinderThread + @Override + public void reportPreRendered(EditorInfo info) { + mImms.reportPreRendered(mToken, info); + } + + @BinderThread + @Override + public void applyImeVisibility(boolean setVisible) { + mImms.applyImeVisibility(mToken, setVisible); + } } } |