summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tarandeep Singh <tarandeep@google.com> 2019-01-25 11:47:57 -0800
committer Jorim Jaggi <jjaggi@google.com> 2019-01-28 16:58:32 +0100
commit2cbcd7ffbf689591a234bee7c416ecf16bd1e684 (patch)
treecfc36075960b55ecd71eef4b03ff9ee2a24fb2e9
parent5cccc2bd8eae729c60c680fe397ed9472496e23c (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
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java29
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java157
-rw-r--r--core/java/android/view/InsetsController.java30
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java14
-rw-r--r--core/java/android/view/ViewRootImpl.java5
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java65
-rw-r--r--core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl5
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java37
-rw-r--r--core/java/com/android/internal/view/IInputMethodClient.aidl4
-rw-r--r--core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java145
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java66
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);
+ }
}
}