Merge "IME transitions without pre-rendering."
This commit is contained in:
@@ -19,6 +19,7 @@ package android.inputmethodservice;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
@@ -62,6 +63,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
@@ -595,12 +597,12 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
if (DEBUG) Log.v(TAG, "hideSoftInput()");
|
||||
final boolean wasVisible = mIsPreRendered
|
||||
? mDecorViewVisible && mWindowVisible : isInputViewShown();
|
||||
applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */);
|
||||
if (mIsPreRendered) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Making IME window invisible");
|
||||
}
|
||||
setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition);
|
||||
applyVisibilityInInsetsConsumer(false /* setVisible */);
|
||||
onPreRenderedWindowVisibilityChanged(false /* setVisible */);
|
||||
} else {
|
||||
mShowInputFlags = 0;
|
||||
@@ -632,11 +634,11 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Making IME window visible");
|
||||
}
|
||||
applyVisibilityInInsetsConsumer(true /* setVisible */);
|
||||
onPreRenderedWindowVisibilityChanged(true /* setVisible */);
|
||||
} else {
|
||||
showWindow(true);
|
||||
}
|
||||
applyVisibilityInInsetsConsumerIfNecessary(true /* setVisible */);
|
||||
}
|
||||
// If user uses hard keyboard, IME button should always be shown.
|
||||
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
|
||||
@@ -1974,16 +1976,20 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
|
||||
/**
|
||||
* Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when
|
||||
* pre-rendering is enabled.
|
||||
* {@link ViewRootImpl.sNewInsetsMode} is enabled.
|
||||
* @param setVisible {@code true} to make it visible, false to hide it.
|
||||
*/
|
||||
private void applyVisibilityInInsetsConsumer(boolean setVisible) {
|
||||
if (!mIsPreRendered) {
|
||||
private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) {
|
||||
if (!isVisibilityAppliedUsingInsetsConsumer()) {
|
||||
return;
|
||||
}
|
||||
mPrivOps.applyImeVisibility(setVisible);
|
||||
}
|
||||
|
||||
private boolean isVisibilityAppliedUsingInsetsConsumer() {
|
||||
return ViewRootImpl.sNewInsetsMode > NEW_INSETS_MODE_NONE;
|
||||
}
|
||||
|
||||
private void finishViews(boolean finishingInput) {
|
||||
if (mInputViewStarted) {
|
||||
if (DEBUG) Log.v(TAG, "CALL: onFinishInputView");
|
||||
@@ -2007,7 +2013,11 @@ public class InputMethodService extends AbstractInputMethodService {
|
||||
mWindowVisible = false;
|
||||
finishViews(false /* finishingInput */);
|
||||
if (mDecorViewVisible) {
|
||||
mWindow.hide();
|
||||
// When insets API is enabled, it is responsible for client and server side
|
||||
// visibility of IME window.
|
||||
if (!isVisibilityAppliedUsingInsetsConsumer()) {
|
||||
mWindow.hide();
|
||||
}
|
||||
mDecorViewVisible = false;
|
||||
onWindowHidden();
|
||||
mDecorViewWasVisible = false;
|
||||
|
||||
@@ -42,7 +42,6 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
||||
* editor {@link #mFocusedEditor} if {@link #isServedEditorRendered} is {@code true}.
|
||||
*/
|
||||
private boolean mShowOnNextImeRender;
|
||||
private boolean mHasWindowFocus;
|
||||
|
||||
public ImeInsetsSourceConsumer(
|
||||
InsetsState state, Supplier<Transaction> transactionSupplier,
|
||||
@@ -68,23 +67,18 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
|
||||
}
|
||||
|
||||
public void applyImeVisibility(boolean setVisible) {
|
||||
if (!mHasWindowFocus) {
|
||||
// App window doesn't have focus, any visibility changes would be no-op.
|
||||
return;
|
||||
}
|
||||
|
||||
mController.applyImeVisibility(setVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusGained() {
|
||||
mHasWindowFocus = true;
|
||||
super.onWindowFocusGained();
|
||||
getImm().registerImeConsumer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusLost() {
|
||||
mHasWindowFocus = false;
|
||||
super.onWindowFocusLost();
|
||||
getImm().unregisterImeConsumer(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,8 @@ public class InsetsController implements WindowInsetsController {
|
||||
final ArraySet<Integer> internalTypes = mState.toInternalType(types);
|
||||
final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
|
||||
|
||||
Pair<Integer, Boolean> typesReadyPair = collectConsumers(fromIme, internalTypes, consumers);
|
||||
Pair<Integer, Boolean> typesReadyPair = collectConsumers(
|
||||
fromIme, internalTypes, consumers, listener);
|
||||
int typesReady = typesReadyPair.first;
|
||||
boolean isReady = typesReadyPair.second;
|
||||
if (!isReady) {
|
||||
@@ -324,13 +325,16 @@ public class InsetsController implements WindowInsetsController {
|
||||
* @return Pair of (types ready to animate, is ready to animate).
|
||||
*/
|
||||
private Pair<Integer, Boolean> collectConsumers(boolean fromIme,
|
||||
ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers) {
|
||||
ArraySet<Integer> internalTypes, SparseArray<InsetsSourceConsumer> consumers,
|
||||
WindowInsetsAnimationControlListener listener) {
|
||||
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()) {
|
||||
// Double check for IME that IME target window has focus.
|
||||
if (consumer.getType() != TYPE_IME || consumer.hasWindowFocus()) {
|
||||
boolean setVisible = !consumer.isVisible();
|
||||
if (setVisible) {
|
||||
// Show request
|
||||
switch(consumer.requestShow(fromIme)) {
|
||||
case ShowResult.SHOW_IMMEDIATELY:
|
||||
@@ -357,8 +361,11 @@ public class InsetsController implements WindowInsetsController {
|
||||
}
|
||||
consumers.put(consumer.getType(), consumer);
|
||||
} else {
|
||||
// TODO: Let calling app know it's not possible, or wait
|
||||
// TODO: Remove it from types
|
||||
// window doesnt have focus, no-op.
|
||||
isReady = false;
|
||||
// TODO: Let the calling app know that window has lost focus and
|
||||
// show()/hide()/controlWindowInsetsAnimation requests will be ignored.
|
||||
typesReady &= ~InsetsState.toPublicType(consumer.getType());
|
||||
}
|
||||
}
|
||||
return new Pair<>(typesReady, isReady);
|
||||
@@ -533,7 +540,10 @@ public class InsetsController implements WindowInsetsController {
|
||||
|
||||
@Override
|
||||
public void onCancelled() {
|
||||
mAnimator.cancel();
|
||||
// Animator can be null when it is cancelled before onReady() completes.
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void onAnimationFinish() {
|
||||
|
||||
@@ -58,6 +58,7 @@ public class InsetsSourceConsumer {
|
||||
private final @InternalInsetType int mType;
|
||||
private final InsetsState mState;
|
||||
private @Nullable InsetsSourceControl mSourceControl;
|
||||
private boolean mHasWindowFocus;
|
||||
|
||||
public InsetsSourceConsumer(@InternalInsetType int type, InsetsState state,
|
||||
Supplier<Transaction> transactionSupplier, InsetsController controller) {
|
||||
@@ -104,12 +105,20 @@ public class InsetsSourceConsumer {
|
||||
/**
|
||||
* Called when current window gains focus
|
||||
*/
|
||||
public void onWindowFocusGained() {}
|
||||
public void onWindowFocusGained() {
|
||||
mHasWindowFocus = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when current window loses focus.
|
||||
*/
|
||||
public void onWindowFocusLost() {}
|
||||
public void onWindowFocusLost() {
|
||||
mHasWindowFocus = false;
|
||||
}
|
||||
|
||||
boolean hasWindowFocus() {
|
||||
return mHasWindowFocus;
|
||||
}
|
||||
|
||||
boolean applyLocalVisibilityOverride() {
|
||||
|
||||
@@ -153,7 +162,6 @@ public class InsetsSourceConsumer {
|
||||
return;
|
||||
}
|
||||
mVisible = visible;
|
||||
applyHiddenToControl();
|
||||
applyLocalVisibilityOverride();
|
||||
mController.notifyVisibilityChanged();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.view;
|
||||
|
||||
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
|
||||
import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
|
||||
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
|
||||
import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
|
||||
import static android.view.WindowInsets.Type.SIZE;
|
||||
@@ -161,13 +162,15 @@ public class InsetsState implements Parcelable {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean skipNonImeInImeMode = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_IME
|
||||
&& source.getType() != TYPE_IME;
|
||||
boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
|
||||
&& (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR);
|
||||
boolean skipIme = source.getType() == TYPE_IME
|
||||
&& (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
|
||||
boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
|
||||
&& (toPublicType(type) & Type.compatSystemInsets()) != 0;
|
||||
if (skipSystemBars || skipIme || skipLegacyTypes) {
|
||||
if (skipSystemBars || skipIme || skipLegacyTypes || skipNonImeInImeMode) {
|
||||
typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ public class InsetsControllerTest {
|
||||
InsetsSourceControl ime = controls[2];
|
||||
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
|
||||
mController.getSourceConsumer(TYPE_IME).onWindowFocusGained();
|
||||
// since there is no focused view, forcefully make IME visible.
|
||||
mController.applyImeVisibility(true /* setVisible */);
|
||||
mController.show(Type.all());
|
||||
@@ -160,6 +161,7 @@ public class InsetsControllerTest {
|
||||
assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
|
||||
assertFalse(mController.getSourceConsumer(topBar.getType()).isVisible());
|
||||
assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
|
||||
mController.getSourceConsumer(TYPE_IME).onWindowFocusLost();
|
||||
});
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
@@ -172,12 +174,14 @@ public class InsetsControllerTest {
|
||||
controls[0] = ime;
|
||||
mController.onControlsChanged(controls);
|
||||
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
|
||||
mController.getSourceConsumer(TYPE_IME).onWindowFocusGained();
|
||||
mController.applyImeVisibility(true);
|
||||
mController.cancelExistingAnimation();
|
||||
assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
|
||||
mController.applyImeVisibility(false);
|
||||
mController.cancelExistingAnimation();
|
||||
assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
|
||||
mController.getSourceConsumer(TYPE_IME).onWindowFocusLost();
|
||||
});
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ package android.view;
|
||||
|
||||
import static android.view.InsetsState.TYPE_TOP_BAR;
|
||||
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
@@ -40,6 +42,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/**
|
||||
@@ -60,6 +63,7 @@ public class InsetsSourceConsumerTest {
|
||||
private SurfaceSession mSession = new SurfaceSession();
|
||||
private SurfaceControl mLeash;
|
||||
@Mock Transaction mMockTransaction;
|
||||
private InsetsSource mSpyInsetsSource;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
@@ -77,7 +81,11 @@ public class InsetsSourceConsumerTest {
|
||||
} catch (BadTokenException e) {
|
||||
// activity isn't running, lets ignore BadTokenException.
|
||||
}
|
||||
mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, new InsetsState(),
|
||||
InsetsState state = new InsetsState();
|
||||
mSpyInsetsSource = Mockito.spy(new InsetsSource(TYPE_TOP_BAR));
|
||||
state.addSource(mSpyInsetsSource);
|
||||
|
||||
mConsumer = new InsetsSourceConsumer(TYPE_TOP_BAR, state,
|
||||
() -> mMockTransaction, new InsetsController(viewRootImpl));
|
||||
});
|
||||
instrumentation.waitForIdleSync();
|
||||
@@ -88,14 +96,15 @@ public class InsetsSourceConsumerTest {
|
||||
@Test
|
||||
public void testHide() {
|
||||
mConsumer.hide();
|
||||
verify(mMockTransaction).hide(eq(mLeash));
|
||||
assertFalse("Consumer should not be visible", mConsumer.isVisible());
|
||||
verify(mSpyInsetsSource).setVisible(eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShow() {
|
||||
mConsumer.hide();
|
||||
mConsumer.show();
|
||||
verify(mMockTransaction, atLeastOnce()).show(eq(mLeash));
|
||||
assertTrue("Consumer should be visible", mConsumer.isVisible());
|
||||
verify(mSpyInsetsSource).setVisible(eq(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user