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

Commit f499c939 authored by Hiroki Sato's avatar Hiroki Sato
Browse files

Makes sure ImeISP invokes onImeRequestedChanged

This makes sure that OnImeRequestedChangedListener#onImeRequestedChanged
is invoked with the latest window token and visibility when IME input
target changes with RemoteInsetsControlTarget.

In multi-window mode, notifying the IME input target change to IMMS
depends on that calling setImeInputTargetRequestedVisibility on
RemoteInsetsControlTarget eventually calls back system server the
visibility change. However, this can only happen the requested
visibility changes. It's possible that the reuqested visibiltiy is the
same but input target changes, which happens when the input target
window moves in between the connected displays.

This is a similar fix as [1] but for different call path.

[1] I96ffe688ae5eb32e325f3f7c9c62b392f7287100

Bug: 438569349
Bug: 411099169
Test: ImeInsetsSourceProviderTest
Test: Open freeform window on desktop, focus on an editor. Moving it to another display restarts input
Flag: EXEMPT bug fix
Change-Id: Ia2666bc96bb6133830a90711bce29bcccfec91e8
parent 18f6c440
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -443,11 +443,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
                        imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED
                                : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
                        false /* fromUser */);
                boolean controlTargetRequestedVisible = imeControlTarget != null
                        && imeControlTarget.isRequestedVisible(WindowInsets.Type.ime());
                if (imeVisible == controlTargetRequestedVisible && imeControlTarget != null) {
                    // Notifying request visibility is no-op, but we need to invoke the listener.
                    invokeOnImeRequestedChangedListener(imeControlTarget, statsToken);
                } else {
                    reportImeInputTargetStateToControlTarget(imeInputTarget, imeControlTarget,
                            statsToken);
                }
            }
        }
    }

    private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInputTarget,
            InsetsControlTarget controlTarget, @NonNull ImeTracker.Token statsToken) {
+97 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.graphics.PixelFormat;
import android.os.RemoteException;
@@ -44,6 +45,7 @@ import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

/**
 * Tests for the {@link ImeInsetsSourceProvider} class.
@@ -57,11 +59,16 @@ import org.junit.runner.RunWith;
public class ImeInsetsSourceProviderTest extends WindowTestsBase {

    private ImeInsetsSourceProvider mImeProvider;
    private WindowManagerInternal.OnImeRequestedChangedListener mMockListener;

    @Before
    public void setUp() throws Exception {
        mImeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider();
        mImeProvider.getSource().setVisible(true);
        mMockListener = Mockito.mock(
                WindowManagerInternal.OnImeRequestedChangedListener.class);
        mWm.mOnImeRequestedChangedListener = mMockListener;

        mWm.mAnimator.ready();
    }

@@ -152,19 +159,26 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
        assertTrue((controlTarget.isRequestedVisible(WindowInsets.Type.ime())));
        mImeProvider.updateControlForTarget(controlTarget, true /* force */,
                ImeTracker.Token.empty());
        waitUntilHandlersIdle();
        verify(displayWindowInsetsController, never()).setImeInputTargetRequestedVisibility(
                anyBoolean(), any());
        verify(mMockListener, times(1)).onImeRequestedChanged(
                eq(inputTarget.getWindowToken()), eq(true), any());

        // Test for not visible
        inputTarget.setRequestedVisibleTypes(0);
        controlTarget.updateRequestedVisibleTypes(0 /* visibleTypes */, WindowInsets.Type.ime());
        clearInvocations(mDisplayContent);
        clearInvocations(mMockListener);
        assertFalse(inputTarget.isRequestedVisible(WindowInsets.Type.ime()));
        assertFalse((controlTarget.isRequestedVisible(WindowInsets.Type.ime())));
        mImeProvider.updateControlForTarget(controlTarget, true /* force */,
                ImeTracker.Token.empty());
        waitUntilHandlersIdle();
        verify(displayWindowInsetsController, never()).setImeInputTargetRequestedVisibility(
                anyBoolean(), any());
        verify(mMockListener, times(1)).onImeRequestedChanged(
                eq(inputTarget.getWindowToken()), eq(false), any());
    }

    @Test
@@ -256,4 +270,87 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase {
        mImeProvider.updateControlForTarget(newTarget, false /* force */, ImeTracker.Token.empty());
        verify(mDisplayContent, never()).getImeInputTarget();
    }

    @Test
    public void testOnImeInputTargetChanged_invokesDisplayWindowInsetsController()
            throws RemoteException {
        final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
        makeWindowVisibleAndDrawn(ime);
        mImeProvider.setWindow(ime, null, null);
        mImeProvider.setServerVisible(true);
        mImeProvider.setClientVisible(true);

        final WindowState inputTarget = newWindowBuilder("app", TYPE_APPLICATION).build();
        final var displayWindowInsetsController = spy(createDisplayWindowInsetsController());
        mDisplayContent.setRemoteInsetsController(displayWindowInsetsController);
        final var remoteControlTarget = mDisplayContent.mRemoteInsetsControlTarget;
        mDisplayContent.setImeInputTarget(inputTarget);
        mImeProvider.updateControlForTarget(
                remoteControlTarget, /* force= */ true, /* token= */ null);

        // IME should be visible, but remote control target currently doesn't request it
        inputTarget.setRequestedVisibleTypes(WindowInsets.Type.ime());
        remoteControlTarget.updateRequestedVisibleTypes(0, WindowInsets.Type.ime());
        clearInvocations(displayWindowInsetsController);

        assertTrue(inputTarget.isRequestedVisible(WindowInsets.Type.ime()));
        assertFalse(remoteControlTarget.isRequestedVisible(WindowInsets.Type.ime()));

        mImeProvider.onImeInputTargetChanged(inputTarget);

        verify(displayWindowInsetsController, times(1)).setImeInputTargetRequestedVisibility(
                eq(true), any());
        verifyNoMoreInteractions(displayWindowInsetsController);
    }

    @Test
    public void testOnImeInputTargetChanged_sameVisibility_invokesListener()
            throws RemoteException {
        final WindowState ime = newWindowBuilder("ime", TYPE_INPUT_METHOD).build();
        makeWindowVisibleAndDrawn(ime);
        mImeProvider.setWindow(ime, null, null);
        mImeProvider.setServerVisible(true); // Ensure provider thinks IME *could* be showing

        final WindowState inputTarget = newWindowBuilder("app", TYPE_APPLICATION).build();
        final var displayWindowInsetsController = spy(createDisplayWindowInsetsController());
        mDisplayContent.setRemoteInsetsController(displayWindowInsetsController);
        final var remoteControlTarget = mDisplayContent.mRemoteInsetsControlTarget;
        mDisplayContent.setImeInputTarget(inputTarget);
        mImeProvider.updateControlForTarget(
                remoteControlTarget, /* force= */ true, /* token= */ null);

        // IME should be visible, and remote control target already requests it
        inputTarget.setRequestedVisibleTypes(WindowInsets.Type.ime());
        remoteControlTarget.updateRequestedVisibleTypes(WindowInsets.Type.ime(),
                WindowInsets.Type.ime());
        clearInvocations(displayWindowInsetsController);
        assertTrue(inputTarget.isRequestedVisible(WindowInsets.Type.ime()));
        assertTrue(remoteControlTarget.isRequestedVisible(WindowInsets.Type.ime()));

        // Even though visibilities match, the listener should still be invoked.
        mImeProvider.onImeInputTargetChanged(inputTarget);
        waitUntilHandlersIdle();

        verify(displayWindowInsetsController, never()).setImeInputTargetRequestedVisibility(
                eq(true), any());
        verify(mMockListener, times(1)).onImeRequestedChanged(
                eq(inputTarget.getWindowToken()), eq(true), any());


        // The same test for invisible.
        inputTarget.setRequestedVisibleTypes(0);
        remoteControlTarget.updateRequestedVisibleTypes(0, WindowInsets.Type.ime());
        clearInvocations(displayWindowInsetsController);
        clearInvocations(mMockListener);
        assertFalse(inputTarget.isRequestedVisible(WindowInsets.Type.ime()));
        assertFalse(remoteControlTarget.isRequestedVisible(WindowInsets.Type.ime()));

        mImeProvider.onImeInputTargetChanged(inputTarget);
        waitUntilHandlersIdle();

        verify(displayWindowInsetsController, never()).setImeInputTargetRequestedVisibility(
                eq(true), any());
        verify(mMockListener, times(1)).onImeRequestedChanged(
                eq(inputTarget.getWindowToken()), eq(false), any());
    }
}