summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java10
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java14
-rw-r--r--core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java6
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java32
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java3
-rw-r--r--core/java/android/view/InsetsController.java129
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java41
-rw-r--r--core/java/android/view/InsetsSourceControl.java19
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java30
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java7
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java3
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java35
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java187
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java3
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java13
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java5
19 files changed, 497 insertions, 49 deletions
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 843db6d28d30..ffae361e76d4 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -51,6 +51,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_TOGGLE_SOFT_INPUT = 105;
private static final int DO_FINISH_SESSION = 110;
private static final int DO_VIEW_CLICKED = 115;
+ private static final int DO_NOTIFY_IME_HIDDEN = 120;
HandlerCaller mCaller;
InputMethodSession mInputMethodSession;
@@ -129,6 +130,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.viewClicked(msg.arg1 == 1);
return;
}
+ case DO_NOTIFY_IME_HIDDEN: {
+ mInputMethodSession.notifyImeHidden();
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -172,6 +177,11 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
@Override
+ public void notifyImeHidden() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN));
+ }
+
+ @Override
public void updateCursor(Rect newCursor) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 333cfbd400dd..ab630fd7467b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -592,7 +592,6 @@ public class InputMethodService extends AbstractInputMethodService {
final boolean wasVisible = mIsPreRendered
? mDecorViewVisible && mWindowVisible : isInputViewShown();
if (mIsPreRendered) {
- // TODO: notify visibility to insets consumer.
if (DEBUG) {
Log.v(TAG, "Making IME window invisible");
}
@@ -658,6 +657,11 @@ public class InputMethodService extends AbstractInputMethodService {
}
}
+ private void notifyImeHidden() {
+ setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
+ onPreRenderedWindowVisibilityChanged(false /* setVisible */);
+ }
+
private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
}
@@ -760,6 +764,14 @@ public class InputMethodService extends AbstractInputMethodService {
}
InputMethodService.this.onUpdateCursorAnchorInfo(info);
}
+
+ /**
+ * Notify IME that window is hidden.
+ * @hide
+ */
+ public final void notifyImeHidden() {
+ InputMethodService.this.notifyImeHidden();
+ }
}
/**
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index b4b541dc5cd0..31c948a14698 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -290,6 +290,12 @@ final class MultiClientInputMethodClientCallbackAdaptor {
CallbackImpl::updateCursorAnchorInfo, mCallbackImpl, info));
}
}
+
+ @Override
+ public final void notifyImeHidden() {
+ // no-op for multi-session since IME is responsible controlling navigation bar buttons.
+ reportNotSupported();
+ }
}
private static final class MultiClientInputMethodSessionImpl
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 7026d2b1389c..2ba1e016e03d 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -18,10 +18,10 @@ package android.view;
import static android.view.InsetsState.TYPE_IME;
+import android.inputmethodservice.InputMethodService;
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;
@@ -73,11 +73,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
return;
}
- if (setVisible) {
- mController.show(Type.IME);
- } else {
- mController.hide(Type.IME);
- }
+ mController.applyImeVisibility(setVisible);
}
@Override
@@ -91,6 +87,30 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
mHasWindowFocus = false;
}
+ /**
+ * Request {@link InputMethodManager} to show the IME.
+ * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
+ */
+ @Override
+ @ShowResult int requestShow(boolean fromIme) {
+ // TODO: ResultReceiver for IME.
+ // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
+ if (fromIme) {
+ return ShowResult.SHOW_IMMEDIATELY;
+ }
+
+ return getImm().requestImeShow(null /* resultReceiver */)
+ ? ShowResult.SHOW_DELAYED : ShowResult.SHOW_FAILED;
+ }
+
+ /**
+ * Notify {@link InputMethodService} that IME window is hidden.
+ */
+ @Override
+ void notifyHidden() {
+ getImm().notifyImeHidden();
+ }
+
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);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 625ddc2549cc..583651dee379 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -180,9 +180,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll
for (int i = items.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = items.valueAt(i);
final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
+ final InsetsSourceControl control = consumer.getControl();
final SurfaceControl leash = consumer.getControl().getLeash();
- mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top);
+ mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
mTmpFrame.set(source.getFrame());
addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 93a6741ae303..7ad97a6d393e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -29,10 +29,13 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.Property;
import android.util.SparseArray;
+import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetType;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -54,6 +57,7 @@ public class InsetsController implements WindowInsetsController {
private static final int DIRECTION_NONE = 0;
private static final int DIRECTION_SHOW = 1;
private static final int DIRECTION_HIDE = 2;
+
@IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
private @interface AnimationDirection{}
@@ -106,6 +110,8 @@ public class InsetsController implements WindowInsetsController {
private ObjectAnimator mAnimator;
private @AnimationDirection int mAnimationDirection;
+ private int mPendingTypesToShow;
+
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
mAnimCallback = () -> {
@@ -196,6 +202,12 @@ public class InsetsController implements WindowInsetsController {
@Override
public void show(@InsetType int types) {
+ show(types, false /* fromIme */);
+ }
+
+ private void show(@InsetType int types, boolean fromIme) {
+ // TODO: Support a ResultReceiver for IME.
+ // TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -204,15 +216,18 @@ public class InsetsController implements WindowInsetsController {
// Only one animator (with multiple InsetType) can run at a time.
// previous one should be cancelled for simplicity.
cancelExistingAnimation();
- } else if (consumer.isVisible() || mAnimationDirection == DIRECTION_SHOW) {
- // no-op: already shown or animating in.
+ } else if (consumer.isVisible()
+ && (mAnimationDirection == DIRECTION_NONE
+ || mAnimationDirection == DIRECTION_HIDE)) {
+ // no-op: already shown or animating in (because window visibility is
+ // applied before starting animation).
// TODO: When we have more than one types: handle specific case when
// show animation is going on, but the current type is not becoming visible.
continue;
}
typesReady |= InsetsState.toPublicType(consumer.getType());
}
- applyAnimation(typesReady, true /* show */);
+ applyAnimation(typesReady, true /* show */, fromIme);
}
@Override
@@ -223,35 +238,114 @@ public class InsetsController implements WindowInsetsController {
InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
if (mAnimationDirection == DIRECTION_SHOW) {
cancelExistingAnimation();
- } else if (!consumer.isVisible() || mAnimationDirection == DIRECTION_HIDE) {
+ } else if (!consumer.isVisible()
+ && (mAnimationDirection == DIRECTION_NONE
+ || mAnimationDirection == DIRECTION_HIDE)) {
// no-op: already hidden or animating out.
continue;
}
typesReady |= InsetsState.toPublicType(consumer.getType());
}
- applyAnimation(typesReady, false /* show */);
+ applyAnimation(typesReady, false /* show */, false /* fromIme */);
}
@Override
public void controlWindowInsetsAnimation(@InsetType int types,
WindowInsetsAnimationControlListener listener) {
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */);
+ }
+
+ private void controlWindowInsetsAnimation(@InsetType int types,
+ WindowInsetsAnimationControlListener listener, boolean fromIme) {
+ if (types == 0) {
+ // nothing to animate.
+ return;
+ }
// TODO: Check whether we already have a controller.
final ArraySet<Integer> internalTypes = mState.toInternalType(types);
final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
+
+ Pair<Integer, Boolean> typesReadyPair = collectConsumers(fromIme, internalTypes, consumers);
+ int typesReady = typesReadyPair.first;
+ boolean isReady = typesReadyPair.second;
+ if (!isReady) {
+ // IME isn't ready, all requested types would be shown once IME is ready.
+ mPendingTypesToShow = typesReady;
+ // TODO: listener for pending types.
+ return;
+ }
+
+ // pending types from previous request.
+ typesReady = collectPendingConsumers(typesReady, consumers);
+
+ if (typesReady == 0) {
+ listener.onCancelled();
+ return;
+ }
+
+ final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
+ mFrame, mState, listener, typesReady,
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
+ mAnimationControls.add(controller);
+ }
+
+ /**
+ * @return Pair of (types ready to animate, is ready to animate).
+ */
+ private Pair<Integer, Boolean> collectConsumers(boolean fromIme,
+ ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers) {
+ int typesReady = 0;
+ boolean isReady = true;
for (int i = internalTypes.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
if (consumer.getControl() != null) {
+ if (!consumer.isVisible()) {
+ // Show request
+ switch(consumer.requestShow(fromIme)) {
+ case ShowResult.SHOW_IMMEDIATELY:
+ typesReady |= InsetsState.toPublicType(TYPE_IME);
+ break;
+ case ShowResult.SHOW_DELAYED:
+ isReady = false;
+ break;
+ case ShowResult.SHOW_FAILED:
+ // IME cannot be shown (since it didn't have focus), proceed
+ // with animation of other types.
+ if (mPendingTypesToShow != 0) {
+ // remove IME from pending because view no longer has focus.
+ mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
+ }
+ break;
+ }
+ } else {
+ // Hide request
+ // TODO: Move notifyHidden() to beginning of the hide animation
+ // (when visibility actually changes using hideDirectly()).
+ consumer.notifyHidden();
+ typesReady |= InsetsState.toPublicType(consumer.getType());
+ }
consumers.put(consumer.getType(), consumer);
} else {
// TODO: Let calling app know it's not possible, or wait
// TODO: Remove it from types
}
}
- final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
- mFrame, mState, listener, types,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
- mAnimationControls.add(controller);
+ return new Pair<>(typesReady, isReady);
+ }
+
+ private int collectPendingConsumers(@InsetType int typesReady,
+ SparseArray<InsetsSourceConsumer> consumers) {
+ if (mPendingTypesToShow != 0) {
+ typesReady |= mPendingTypesToShow;
+ final ArraySet<Integer> internalTypes = mState.toInternalType(mPendingTypesToShow);
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+ consumers.put(consumer.getType(), consumer);
+ }
+ mPendingTypesToShow = 0;
+ }
+ return typesReady;
}
private void applyLocalVisibilityOverride() {
@@ -296,6 +390,19 @@ public class InsetsController implements WindowInsetsController {
return mViewRoot;
}
+ /**
+ * Used by {@link ImeInsetsSourceConsumer} when IME decides to be shown/hidden.
+ * @hide
+ */
+ @VisibleForTesting
+ public void applyImeVisibility(boolean setVisible) {
+ if (setVisible) {
+ show(Type.IME, true /* fromIme */);
+ } else {
+ hide(Type.IME);
+ }
+ }
+
private InsetsSourceConsumer createConsumerOfType(int type) {
if (type == TYPE_IME) {
return new ImeInsetsSourceConsumer(mState, Transaction::new, this);
@@ -324,7 +431,7 @@ public class InsetsController implements WindowInsetsController {
}
}
- private void applyAnimation(@InsetType final int types, boolean show) {
+ private void applyAnimation(@InsetType final int types, boolean show, boolean fromIme) {
if (types == 0) {
// nothing to animate.
return;
@@ -372,7 +479,7 @@ public class InsetsController implements WindowInsetsController {
// TODO: Instead of clearing this here, properly wire up
// InsetsAnimationControlImpl.finish() to remove this from mAnimationControls.
mAnimationControls.clear();
- controlWindowInsetsAnimation(types, listener);
+ controlWindowInsetsAnimation(types, listener, fromIme);
}
private void hideDirectly(@InsetType int types) {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index cccfd870a3e4..eab83ce34708 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -16,12 +16,15 @@
package android.view;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.view.InsetsState.InternalInsetType;
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.function.Supplier;
/**
@@ -30,6 +33,25 @@ import java.util.function.Supplier;
*/
public class InsetsSourceConsumer {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.SHOW_DELAYED, ShowResult.SHOW_FAILED})
+ @interface ShowResult {
+ /**
+ * Window type is ready to be shown, will be shown immidiately.
+ */
+ int SHOW_IMMEDIATELY = 0;
+ /**
+ * Result will be delayed. Window needs to be prepared or request is not from controller.
+ * Request will be delegated to controller and may or may not be shown.
+ */
+ int SHOW_DELAYED = 1;
+ /**
+ * Window will not be shown because one of the conditions couldn't be met.
+ * (e.g. in IME's case, when no editor is focused.)
+ */
+ int SHOW_FAILED = 2;
+ }
+
protected final InsetsController mController;
protected boolean mVisible;
private final Supplier<Transaction> mTransactionSupplier;
@@ -104,6 +126,25 @@ public class InsetsSourceConsumer {
return mVisible;
}
+ /**
+ * Request to show current window type.
+ *
+ * @param fromController {@code true} if request is coming from controller.
+ * (e.g. in IME case, controller is
+ * {@link android.inputmethodservice.InputMethodService}).
+ * @return @see {@link ShowResult}.
+ */
+ @ShowResult int requestShow(boolean fromController) {
+ return ShowResult.SHOW_IMMEDIATELY;
+ }
+
+ /**
+ * Notify listeners that window is now hidden.
+ */
+ void notifyHidden() {
+ // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
+ }
+
private void setVisible(boolean visible) {
if (mVisible == visible) {
return;
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 9383e6c57854..9fb6bdba48e3 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -29,10 +29,13 @@ public class InsetsSourceControl implements Parcelable {
private final @InternalInsetType int mType;
private final SurfaceControl mLeash;
+ private final Point mSurfacePosition;
- public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash) {
+ public InsetsSourceControl(@InternalInsetType int type, SurfaceControl leash,
+ Point surfacePosition) {
mType = type;
mLeash = leash;
+ mSurfacePosition = surfacePosition;
}
public int getType() {
@@ -46,6 +49,19 @@ public class InsetsSourceControl implements Parcelable {
public InsetsSourceControl(Parcel in) {
mType = in.readInt();
mLeash = in.readParcelable(null /* loader */);
+ mSurfacePosition = in.readParcelable(null /* loader */);
+ }
+
+ public boolean setSurfacePosition(int left, int top) {
+ if (mSurfacePosition.equals(left, top)) {
+ return false;
+ }
+ mSurfacePosition.set(left, top);
+ return true;
+ }
+
+ public Point getSurfacePosition() {
+ return mSurfacePosition;
}
@Override
@@ -57,6 +73,7 @@ public class InsetsSourceControl implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeParcelable(mLeash, 0 /* flags*/);
+ dest.writeParcelable(mSurfacePosition, 0 /* flags*/);
}
public static final Creator<InsetsSourceControl> CREATOR
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9a317db84f5d..61fb38f8298f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -180,7 +180,7 @@ public final class ViewRootImpl implements ViewParent,
* @see #USE_NEW_INSETS_PROPERTY
* @hide
*/
- public static final int sNewInsetsMode =
+ public static int sNewInsetsMode =
SystemProperties.getInt(USE_NEW_INSETS_PROPERTY, 0);
/**
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7fee3ef29a09..ce94cb064416 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1887,6 +1887,36 @@ public final class InputMethodManager {
}
/**
+ * Call showSoftInput with currently focused view.
+ * @return {@code true} if IME can be shown.
+ * @hide
+ */
+ public boolean requestImeShow(ResultReceiver resultReceiver) {
+ synchronized (mH) {
+ if (mServedView == null) {
+ return false;
+ }
+ showSoftInput(mServedView, 0 /* flags */, resultReceiver);
+ return true;
+ }
+ }
+
+ /**
+ * Notify IME directly that it is no longer visible.
+ * @hide
+ */
+ public void notifyImeHidden() {
+ synchronized (mH) {
+ try {
+ if (mCurMethod != null) {
+ mCurMethod.notifyImeHidden();
+ }
+ } catch (RemoteException re) {
+ }
+ }
+ }
+
+ /**
* Report the current selection range.
*
* <p><strong>Editor authors</strong>, you need to call this method whenever
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index de15f332d51d..eb81628f9e27 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -184,4 +184,11 @@ public interface InputMethodSession {
* insertion point and composition string.
*/
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
+
+ /**
+ * Notifies {@link android.inputmethodservice.InputMethodService} that IME has been
+ * hidden from user.
+ * @hide
+ */
+ public void notifyImeHidden();
}
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index 794238a3826e..664643cc9b4d 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -48,4 +48,6 @@ oneway interface IInputMethodSession {
void finishSession();
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
+
+ void notifyImeHidden();
}
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index b07cb99f35c5..da81d176de8a 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@ import static junit.framework.Assert.assertTrue;
import android.content.Context;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
@@ -80,7 +81,7 @@ public class ImeInsetsSourceConsumerTest {
@Test
public void testImeVisibility() {
- final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+ final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash, new Point());
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 7cd3c44d9a4e..71ce02d859f5 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,13 +19,23 @@ package android.view;
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
+import static android.view.WindowInsets.Type.sideBars;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.topBar;
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.graphics.Insets;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
@@ -55,6 +65,7 @@ public class InsetsAnimationControlImplTest {
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mTopLeash;
private SurfaceControl mNavLeash;
+ private InsetsState mInsetsState;
@Mock Transaction mMockTransaction;
@Mock InsetsController mMockController;
@@ -63,6 +74,7 @@ public class InsetsAnimationControlImplTest {
@Before
public void setup() {
+ ViewRootImpl.sNewInsetsMode = NEW_INSETS_MODE_FULL;
MockitoAnnotations.initMocks(this);
mTopLeash = new SurfaceControl.Builder(mSession)
.setName("testSurface")
@@ -70,24 +82,25 @@ public class InsetsAnimationControlImplTest {
mNavLeash = new SurfaceControl.Builder(mSession)
.setName("testSurface")
.build();
- InsetsState state = new InsetsState();
- state.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 500, 100));
- state.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
- InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, state,
+ mInsetsState = new InsetsState();
+ mInsetsState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 500, 100));
+ mInsetsState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
+ InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, mInsetsState,
() -> mMockTransaction, mMockController);
- topConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mTopLeash));
+ topConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mTopLeash, new Point(0, 0)));
- InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(TYPE_NAVIGATION_BAR, state,
- () -> mMockTransaction, mMockController);
+ InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(TYPE_NAVIGATION_BAR,
+ mInsetsState, () -> mMockTransaction, mMockController);
navConsumer.hide();
- navConsumer.setControl(new InsetsSourceControl(TYPE_NAVIGATION_BAR, mNavLeash));
+ navConsumer.setControl(new InsetsSourceControl(TYPE_NAVIGATION_BAR, mNavLeash,
+ new Point(400, 0)));
SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
consumers.put(TYPE_TOP_BAR, topConsumer);
consumers.put(TYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
- new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(),
- () -> mMockTransactionApplier, mock(InsetsController.class));
+ new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
+ () -> mMockTransactionApplier, mMockController);
}
@Test
@@ -95,7 +108,7 @@ public class InsetsAnimationControlImplTest {
assertEquals(Insets.of(0, 100, 100, 0), mController.getShownStateInsets());
assertEquals(Insets.of(0, 0, 0, 0), mController.getHiddenStateInsets());
assertEquals(Insets.of(0, 100, 0, 0), mController.getCurrentInsets());
- assertEquals(WindowInsets.Type.systemBars(), mController.getTypes());
+ assertEquals(systemBars(), mController.getTypes());
}
@Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 8f2109676dfb..6dad6a22f7ea 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -27,6 +27,7 @@ import static junit.framework.Assert.assertTrue;
import android.content.Context;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.WindowInsets.Type;
@@ -74,11 +75,12 @@ public class InsetsControllerTest {
Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
rect, rect);
});
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@Test
public void testControlsChanged() {
- InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
+ InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash, new Point());
mController.onControlsChanged(new InsetsSourceControl[] { control });
assertEquals(mLeash,
mController.getSourceConsumer(TYPE_TOP_BAR).getControl().getLeash());
@@ -86,7 +88,7 @@ public class InsetsControllerTest {
@Test
public void testControlsRevoked() {
- InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
+ InsetsSourceControl control = new InsetsSourceControl(TYPE_TOP_BAR, mLeash, new Point());
mController.onControlsChanged(new InsetsSourceControl[] { control });
mController.onControlsChanged(new InsetsSourceControl[0]);
assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl());
@@ -94,22 +96,19 @@ public class InsetsControllerTest {
@Test
public void testAnimationEndState() {
- final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash);
- final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash);
- final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash);
+ InsetsSourceControl[] controls = prepareControls();
+ InsetsSourceControl navBar = controls[0];
+ InsetsSourceControl topBar = controls[1];
+ InsetsSourceControl ime = controls[2];
- InsetsSourceControl[] controls = new InsetsSourceControl[3];
- controls[0] = navBar;
- controls[1] = topBar;
- controls[2] = ime;
- mController.onControlsChanged(controls);
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.show(Type.all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimation();
assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+ // no focused view, no IME.
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
mController.hide(Type.all());
mController.cancelExistingAnimation();
@@ -119,11 +118,175 @@ public class InsetsControllerTest {
mController.show(Type.ime());
mController.cancelExistingAnimation();
+ // no focused view, no IME.
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testApplyImeVisibility() {
+ final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash, new Point());
+
+ InsetsSourceControl[] controls = new InsetsSourceControl[3];
+ controls[0] = ime;
+ mController.onControlsChanged(controls);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mController.applyImeVisibility(true);
+ mController.cancelExistingAnimation();
assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+ mController.applyImeVisibility(false);
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
- mController.hide(Type.ime());
+ @Test
+ public void testShowHideSelectively() {
+ InsetsSourceControl[] controls = prepareControls();
+ InsetsSourceControl navBar = controls[0];
+ InsetsSourceControl topBar = controls[1];
+ InsetsSourceControl ime = controls[2];
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ int types = Type.sideBars() | Type.systemBars();
+ // test show select types.
+ mController.show(types);
mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ // test hide all
+ mController.hide(types);
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
});
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testShowHideSingle() {
+ InsetsSourceControl[] controls = prepareControls();
+ InsetsSourceControl navBar = controls[0];
+ InsetsSourceControl topBar = controls[1];
+ InsetsSourceControl ime = controls[2];
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ int types = Type.sideBars() | Type.systemBars();
+ // test show select types.
+ mController.show(types);
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ // test hide all
+ mController.hide(Type.all());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ // test single show
+ mController.show(Type.sideBars());
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ // test single hide
+ mController.hide(Type.sideBars());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testShowHideMultiple() {
+ InsetsSourceControl[] controls = prepareControls();
+ InsetsSourceControl navBar = controls[0];
+ InsetsSourceControl topBar = controls[1];
+ InsetsSourceControl ime = controls[2];
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // start two animations and see if previous is cancelled and final state is reached.
+ mController.show(Type.sideBars());
+ mController.show(Type.systemBars());
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.hide(Type.sideBars());
+ mController.hide(Type.systemBars());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ int types = Type.sideBars() | Type.systemBars();
+ // show two at a time and hide one by one.
+ mController.show(types);
+ mController.hide(Type.sideBars());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.hide(Type.systemBars());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ @Test
+ public void testShowMultipleHideOneByOne() {
+ InsetsSourceControl[] controls = prepareControls();
+ InsetsSourceControl navBar = controls[0];
+ InsetsSourceControl topBar = controls[1];
+ InsetsSourceControl ime = controls[2];
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ int types = Type.sideBars() | Type.systemBars();
+ // show two at a time and hide one by one.
+ mController.show(types);
+ mController.hide(Type.sideBars());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+
+ mController.hide(Type.systemBars());
+ mController.cancelExistingAnimation();
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private InsetsSourceControl[] prepareControls() {
+ final InsetsSourceControl navBar = new InsetsSourceControl(TYPE_NAVIGATION_BAR, mLeash,
+ new Point());
+ final InsetsSourceControl topBar = new InsetsSourceControl(TYPE_TOP_BAR, mLeash,
+ new Point());
+ final InsetsSourceControl ime = new InsetsSourceControl(TYPE_IME, mLeash, new Point());
+
+ InsetsSourceControl[] controls = new InsetsSourceControl[3];
+ controls[0] = navBar;
+ controls[1] = topBar;
+ controls[2] = ime;
+ mController.onControlsChanged(controls);
+ return controls;
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 82cd2131ab4e..66146c936dca 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
+import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl.Transaction;
@@ -56,7 +57,7 @@ public class InsetsSourceConsumerTest {
.build();
mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, new InsetsState(),
() -> mMockTransaction, mMockController);
- mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash));
+ mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash, new Point()));
}
@Test
@@ -78,7 +79,7 @@ public class InsetsSourceConsumerTest {
reset(mMockTransaction);
mConsumer.hide();
verifyZeroInteractions(mMockTransaction);
- mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash));
+ mConsumer.setControl(new InsetsSourceControl(TYPE_TOP_BAR, mLeash, new Point()));
verify(mMockTransaction).hide(eq(mLeash));
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 111808b54802..18df88b37c61 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -20,8 +20,10 @@ import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LE
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -3250,6 +3252,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mInputMethodTarget = target;
mInputMethodTargetWaitingAnim = targetWaitingAnim;
assignWindowLayers(false /* setLayoutNeeded */);
+ mInsetsStateController.onImeTargetChanged(target);
}
boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 66666e681e7a..f67b11b26b12 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -26,6 +26,7 @@ import static android.view.ViewRootImpl.sNewInsetsMode;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsState;
@@ -135,6 +136,12 @@ class InsetsSourceProvider {
mTmpRect.inset(mWin.mGivenContentInsets);
}
mSource.setFrame(mTmpRect);
+ if (mControl != null) {
+ final Rect frame = mWin.getWindowFrames().mFrame;
+ if (mControl.setSurfacePosition(frame.left, frame.top)) {
+ mStateController.notifyControlChanged(mControllingWin);
+ }
+ }
setServerVisible(mWin.wouldBeVisibleIfPolicyIgnored() && mWin.mPolicyVisibility
&& !mWin.mGivenInsetsPending);
}
@@ -157,7 +164,8 @@ class InsetsSourceProvider {
mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
!mClientVisible /* hidden */);
mControllingWin = target;
- mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash);
+ mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
+ new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
}
boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
@@ -213,7 +221,8 @@ class InsetsSourceProvider {
public void startAnimation(SurfaceControl animationLeash, Transaction t,
OnAnimationFinishedCallback finishCallback) {
mCapturedLeash = animationLeash;
- t.setPosition(mCapturedLeash, mSource.getFrame().left, mSource.getFrame().top);
+ final Rect frame = mWin.getWindowFrames().mFrame;
+ t.setPosition(mCapturedLeash, frame.left, frame.top);
}
@Override
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index bb0cbb1de470..afae9c4ac228 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -204,6 +204,11 @@ class InsetsStateController {
mTypeWinControlMap.put(type, win);
}
+ void notifyControlChanged(WindowState target) {
+ mPendingControlChanged.add(target);
+ notifyPendingInsetsControlChanged();
+ }
+
private void notifyPendingInsetsControlChanged() {
if (mPendingControlChanged.isEmpty()) {
return;