Merge "IME transitions without pre-rendering."

This commit is contained in:
Taran Singh
2019-08-27 20:57:59 +00:00
committed by Android (Google) Code Review
7 changed files with 68 additions and 30 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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();
}

View File

@@ -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