Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a6f35616 authored by Tarandeep Singh's avatar Tarandeep Singh Committed by Jorim Jaggi
Browse files

Reparent IME window and handle non-fullscreen windows correctly

IME window is attached to the IME target if possible. This ensures
a smooth enter/exit animation when the activity is coming in/going
away.

Furthermore, if the controlling window doesn't span the entire
display, we can't offer controlling it in a frame-by-frame
manner, and we need to do the inset calculations relative to the
display frame.

Test:
adb shell setprop persist.wm.new_insets 1
adb shell setprop persist.pre_render_ime_views 0

Test: Open IME, go home, reopen app
Test: Show dialog with EditText
Bug: 111084606
Change-Id: Id40470f6f8284b48acfa4719049afd14fde332d6
parent 437ed07b
Loading
Loading
Loading
Loading
+21 −3
Original line number Diff line number Diff line
@@ -133,6 +133,10 @@ public class InsetsController implements WindowInsetsController {
    }

    void onFrameChanged(Rect frame) {
        if (mFrame.equals(frame)) {
            return;
        }
        mViewRoot.notifyInsetsChanged();
        mFrame.set(frame);
    }

@@ -257,11 +261,21 @@ public class InsetsController implements WindowInsetsController {

    private void controlWindowInsetsAnimation(@InsetType int types,
            WindowInsetsAnimationControlListener listener, boolean fromIme) {
        // If the frame of our window doesn't span the entire display, the control API makes very
        // little sense, as we don't deal with negative insets. So just cancel immediately.
        if (!mState.getDisplayFrame().equals(mFrame)) {
            listener.onCancelled();
            return;
        }
        controlAnimationUnchecked(types, listener, mFrame, fromIme);
    }

    private void controlAnimationUnchecked(@InsetType int types,
            WindowInsetsAnimationControlListener listener, Rect frame, 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<>();
@@ -285,7 +299,7 @@ public class InsetsController implements WindowInsetsController {
        }

        final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
                mFrame, mState, listener, typesReady,
                frame, mState, listener, typesReady,
                () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
        mAnimationControls.add(controller);
    }
@@ -436,6 +450,7 @@ public class InsetsController implements WindowInsetsController {
            // nothing to animate.
            return;
        }

        WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
            @Override
            public void onReady(WindowInsetsAnimationController controller, int types) {
@@ -479,7 +494,10 @@ 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, fromIme);

        // Show/hide animations always need to be relative to the display frame, in order that shown
        // and hidden state insets are correct.
        controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme);
    }

    private void hideDirectly(@InsetType int types) {
+22 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;

/**
 * Holder for state of system windows that cause window insets for all other windows in the system.
@@ -104,6 +105,11 @@ public class InsetsState implements Parcelable {

    private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>();

    /**
     * The frame of the display these sources are relative to.
     */
    private final Rect mDisplayFrame = new Rect();

    public InsetsState() {
    }

@@ -209,6 +215,14 @@ public class InsetsState implements Parcelable {
        return mSources.computeIfAbsent(type, InsetsSource::new);
    }

    public void setDisplayFrame(Rect frame) {
        mDisplayFrame.set(frame);
    }

    public Rect getDisplayFrame() {
        return mDisplayFrame;
    }

    /**
     * Modifies the state of this class to exclude a certain type to make it ready for dispatching
     * to the client.
@@ -224,6 +238,7 @@ public class InsetsState implements Parcelable {
    }

    public void set(InsetsState other, boolean copySources) {
        mDisplayFrame.set(other.mDisplayFrame);
        mSources.clear();
        if (copySources) {
            for (int i = 0; i < other.mSources.size(); i++) {
@@ -323,6 +338,9 @@ public class InsetsState implements Parcelable {

        InsetsState state = (InsetsState) o;

        if (!mDisplayFrame.equals(state.mDisplayFrame)) {
            return false;
        }
        if (mSources.size() != state.mSources.size()) {
            return false;
        }
@@ -341,7 +359,7 @@ public class InsetsState implements Parcelable {

    @Override
    public int hashCode() {
        return mSources.hashCode();
        return Objects.hash(mDisplayFrame, mSources);
    }

    public InsetsState(Parcel in) {
@@ -355,9 +373,10 @@ public class InsetsState implements Parcelable {

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(mDisplayFrame, flags);
        dest.writeInt(mSources.size());
        for (int i = 0; i < mSources.size(); i++) {
            dest.writeParcelable(mSources.valueAt(i), 0 /* flags */);
            dest.writeParcelable(mSources.valueAt(i), flags);
        }
    }

@@ -374,6 +393,7 @@ public class InsetsState implements Parcelable {

    public void readFromParcel(Parcel in) {
        mSources.clear();
        mDisplayFrame.set(in.readParcelable(null /* loader */));
        final int size = in.readInt();
        for (int i = 0; i < size; i++) {
            final InsetsSource source = in.readParcelable(null /* loader */);
+16 −0
Original line number Diff line number Diff line
@@ -24,6 +24,11 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.graphics.Insets;
@@ -94,6 +99,17 @@ public class InsetsControllerTest {
        assertNull(mController.getSourceConsumer(TYPE_TOP_BAR).getControl());
    }

    @Test
    public void testFrameDoesntMatchDisplay() {
        mController.onFrameChanged(new Rect(0, 0, 100, 100));
        mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
        WindowInsetsAnimationControlListener controlListener =
                mock(WindowInsetsAnimationControlListener.class);
        mController.controlWindowInsetsAnimation(0, controlListener);
        verify(controlListener).onCancelled();
        verify(controlListener, never()).onReady(any(), anyInt());
    }

    @Test
    public void testAnimationEndState() {
        InsetsSourceControl[] controls = prepareControls();
+76 −41
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.util.SparseIntArray;
import android.view.WindowInsets.Type;
import android.view.test.InsetsModeSession;

import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
@@ -53,7 +54,9 @@ public class InsetsStateTest {
    private InsetsState mState2 = new InsetsState();

    @Test
    public void testCalculateInsets() {
    public void testCalculateInsets() throws Exception {
        try (final InsetsModeSession session =
                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
            mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
            mState.getSource(TYPE_TOP_BAR).setVisible(true);
            mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300));
@@ -68,9 +71,12 @@ public class InsetsStateTest {
            assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
            assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.ime()));
        }
    }

    @Test
    public void testCalculateInsets_imeAndNav() {
    public void testCalculateInsets_imeAndNav() throws Exception{
        try (final InsetsModeSession session =
                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
            mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300));
            mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true);
            mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300));
@@ -78,15 +84,18 @@ public class InsetsStateTest {
            WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
                    DisplayCutout.NO_CUTOUT, null, null, null);
            assertEquals(100, insets.getStableInsetBottom());
        assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all()));
            assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.systemBars()));
            assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets());
            assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all()));
            assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.sideBars()));
            assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.ime()));
        }
    }

    @Test
    public void testCalculateInsets_navRightStatusTop() {
    public void testCalculateInsets_navRightStatusTop() throws Exception {
        try (final InsetsModeSession session =
                     new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
            mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
            mState.getSource(TYPE_TOP_BAR).setVisible(true);
            mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
@@ -97,6 +106,7 @@ public class InsetsStateTest {
            assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar()));
            assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars()));
        }
    }

    @Test
    public void testStripForDispatch() {
@@ -114,14 +124,14 @@ public class InsetsStateTest {
    public void testEquals_differentRect() {
        mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
        mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 10, 10));
        assertNotEquals(mState, mState2);
        assertNotEqualsAndHashCode();
    }

    @Test
    public void testEquals_differentSource() {
        mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
        mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100));
        assertNotEquals(mState, mState2);
        assertNotEqualsAndHashCode();
    }

    @Test
@@ -130,7 +140,7 @@ public class InsetsStateTest {
        mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100));
        mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100));
        mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
        assertEquals(mState, mState2);
        assertEqualsAndHashCode();
    }

    @Test
@@ -138,7 +148,21 @@ public class InsetsStateTest {
        mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100));
        mState.getSource(TYPE_IME).setVisible(true);
        mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100));
        assertNotEquals(mState, mState2);
        assertNotEqualsAndHashCode();
    }

    @Test
    public void testEquals_differentFrame() {
        mState.setDisplayFrame(new Rect(0, 1, 2, 3));
        mState.setDisplayFrame(new Rect(4, 5, 6, 7));
        assertNotEqualsAndHashCode();
    }

    @Test
    public void testEquals_sameFrame() {
        mState.setDisplayFrame(new Rect(0, 1, 2, 3));
        mState2.setDisplayFrame(new Rect(0, 1, 2, 3));
        assertEqualsAndHashCode();
    }

    @Test
@@ -148,6 +172,7 @@ public class InsetsStateTest {
        mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100));
        Parcel p = Parcel.obtain();
        mState.writeToParcel(p, 0 /* flags */);
        p.setDataPosition(0);
        mState2.readFromParcel(p);
        p.recycle();
        assertEquals(mState, mState2);
@@ -161,4 +186,14 @@ public class InsetsStateTest {
        assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_3));
        assertFalse(InsetsState.getDefaultVisibility(TYPE_IME));
    }

    private void assertEqualsAndHashCode() {
        assertEquals(mState, mState2);
        assertEquals(mState.hashCode(), mState2.hashCode());
    }

    private void assertNotEqualsAndHashCode() {
        assertNotEquals(mState, mState2);
        assertNotEquals(mState.hashCode(), mState2.hashCode());
    }
}
+37 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
import static android.view.WindowManager.DOCKED_BOTTOM;
import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_TOP;
@@ -172,6 +173,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;

@@ -3251,6 +3253,36 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
        mInputMethodTargetWaitingAnim = targetWaitingAnim;
        assignWindowLayers(false /* setLayoutNeeded */);
        mInsetsStateController.onImeTargetChanged(target);
        updateImeParent();
    }

    private void updateImeParent() {
        if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE) {
            return;
        }
        final SurfaceControl newParent = computeImeParent();
        if (newParent != null) {
            mPendingTransaction.reparent(mImeWindowsContainers.mSurfaceControl, newParent);
            scheduleAnimation();
        }
    }

    /**
     * Computes the window the IME should be attached to.
     */
    @VisibleForTesting
    SurfaceControl computeImeParent() {

        // Attach it to app if the target is part of an app and such app is covering the entire
        // screen. If it's not covering the entire screen the IME might extend beyond the apps
        // bounds.
        if (mInputMethodTarget != null && mInputMethodTarget.mAppToken != null &&
                mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
            return mInputMethodTarget.mAppToken.getSurfaceControl();
        }

        // Otherwise, we just attach it to the display.
        return mWindowingLayer;
    }

    boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) {
@@ -4885,6 +4917,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
                .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc);
    }

    @VisibleForTesting
    SurfaceControl getWindowingLayer() {
        return mWindowingLayer;
    }

    /**
     * Create a portal window handle for input. This window transports any touch to the display
     * indicated by {@link InputWindowHandle#portalToDisplayId} if the touch hits this window.
Loading