diff options
| author | 2019-02-05 03:56:58 +0000 | |
|---|---|---|
| committer | 2019-02-05 03:56:58 +0000 | |
| commit | 73f9f9bf8f69500667f0cbeab873a25e64ba9b4c (patch) | |
| tree | 2da85090e6df36666bed62231a8399cbc695fb60 | |
| parent | d58aa257cddde71c374300e0552aa20cfccb7e54 (diff) | |
| parent | 46d59f0ecb19fc586aecbe3008fd4661f20e98ef (diff) | |
Merge changes Ib3997487,Ifed8351b
* changes:
Link InsetsController to IME (IME transitons 4/n)
Send IME control to client
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; |