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

Commit e818b0e0 authored by mattsziklay's avatar mattsziklay
Browse files

Add unit test for EventReceiver class in CaptionWindowDecorViewModel

Added a unit test to ensure that EventReceivers are correctly created
and destroyed on creation/destruction of WindowDecorations.

Bug: 258271539
Test: atest CaptionWindowDecorViewModelTests
Change-Id: I85ce32508174a17730f4a931d4c81616aedf92e0
parent 9ff21630
Loading
Loading
Loading
Loading
+19 −23
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -56,7 +57,6 @@ import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;

import java.util.Optional;
import java.util.function.Supplier;

/**
 * View model for the window decoration with a caption and shadows. Works with
@@ -66,7 +66,6 @@ import java.util.function.Supplier;
public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
    private static final String TAG = "CaptionViewModel";
    private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
    private final Supplier<InputManager> mInputManagerSupplier;
    private final ActivityTaskManager mActivityTaskManager;
    private final ShellTaskOrganizer mTaskOrganizer;
    private final Context mContext;
@@ -82,7 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {

    private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
    private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
    private InputMonitorFactory mInputMonitorFactory;

    public CaptionWindowDecorViewModel(
            Context context,
@@ -101,10 +100,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
                syncQueue,
                desktopModeController,
                new CaptionWindowDecoration.Factory(),
                InputManager::getInstance);
                new InputMonitorFactory());
    }

    public CaptionWindowDecorViewModel(
    @VisibleForTesting
    CaptionWindowDecorViewModel(
            Context context,
            Handler mainHandler,
            Choreographer mainChoreographer,
@@ -113,8 +113,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            SyncTransactionQueue syncQueue,
            Optional<DesktopModeController> desktopModeController,
            CaptionWindowDecoration.Factory captionWindowDecorFactory,
            Supplier<InputManager> inputManagerSupplier) {

            InputMonitorFactory inputMonitorFactory) {
        mContext = context;
        mMainHandler = mainHandler;
        mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        mDesktopModeController = desktopModeController;

        mCaptionWindowDecorFactory = captionWindowDecorFactory;
        mInputManagerSupplier = inputManagerSupplier;
    }

    void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
        mEventReceiverFactory = eventReceiverFactory;
        mInputMonitorFactory = inputMonitorFactory;
    }

    @Override
@@ -205,7 +200,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        decoration.close();
        int displayId = taskInfo.displayId;
        if (mEventReceiversByDisplay.contains(displayId)) {
            EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
            removeTaskFromEventReceiver(displayId);
        }
    }
@@ -408,12 +402,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
        }
    }

    class EventReceiverFactory {
        EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
            return new EventReceiver(inputMonitor, channel, looper);
        }
    }

    /**
     * Handle MotionEvents relevant to focused task's caption that don't directly touch it
     *
@@ -500,11 +488,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
    }

    private void createInputChannel(int displayId) {
        InputManager inputManager = mInputManagerSupplier.get();
        InputManager inputManager = InputManager.getInstance();
        InputMonitor inputMonitor =
                inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
        EventReceiver eventReceiver = mEventReceiverFactory.create(
                inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
                mInputMonitorFactory.create(inputManager, mContext);
        EventReceiver eventReceiver = new EventReceiver(inputMonitor,
                inputMonitor.getInputChannel(), Looper.myLooper());
        mEventReceiversByDisplay.put(displayId, eventReceiver);
    }

@@ -562,4 +550,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
            mWindowDecorByTaskId.get(taskId).closeHandleMenu();
        }
    }

    static class InputMonitorFactory {
        InputMonitor create(InputManager inputManager, Context context) {
            return inputManager.monitorGestureInput("caption-touch", context.getDisplayId());
        }
    }
}

+119 −90
Original line number Diff line number Diff line
@@ -21,14 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.ActivityManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
@@ -37,9 +38,9 @@ import android.view.Display;
import android.view.InputChannel;
import android.view.InputMonitor;
import android.view.SurfaceControl;
import android.view.SurfaceView;

import androidx.test.filters.SmallTest;
import androidx.test.rule.GrantPermissionRule;

import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@ import org.mockito.Mock;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/** Tests of {@link CaptionWindowDecorViewModel} */
@SmallTest
public class CaptionWindowDecorViewModelTests extends ShellTestCase {
    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;

    private static final String TAG = "CaptionWindowDecorViewModelTests";

    @Mock private CaptionWindowDecoration mCaptionWindowDecoration;
    @Mock private CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;

    @Mock private Handler mMainHandler;

    @Mock private Choreographer mMainChoreographer;

    @Mock private ShellTaskOrganizer mTaskOrganizer;

    @Mock private DisplayController mDisplayController;

    @Mock private SyncTransactionQueue mSyncQueue;

    @Mock private DesktopModeController mDesktopModeController;

    @Mock private InputMonitor mInputMonitor;

    @Mock private InputChannel mInputChannel;

    @Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;

    @Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;

    @Mock private InputManager mInputManager;

    @Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
    private final List<InputManager> mMockInputManagers = new ArrayList<>();

    private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,30 +96,36 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
                mSyncQueue,
                Optional.of(mDesktopModeController),
                mCaptionWindowDecorFactory,
                new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
        mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
                mMockInputMonitorFactory
            );

        doReturn(mCaptionWindowDecoration)
            .when(mCaptionWindowDecorFactory)
            .create(any(), any(), any(), any(), any(), any(), any(), any());

        when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
        when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
        when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
        when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
        // InputChannel cannot be mocked because it passes to InputEventReceiver.
        final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
        inputChannels[0].dispose();
        when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
    }

    @Test
    public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
        Looper.prepare();
        final int taskId = 1;
        final ActivityManager.RunningTaskInfo taskInfo =
                createTaskInfo(taskId, WINDOWING_MODE_FREEFORM);
                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
        SurfaceControl surfaceControl = mock(SurfaceControl.class);
        runOnMainThread(() -> {
            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
        GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);

            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);

            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
        });
        verify(mCaptionWindowDecorFactory)
                .create(
                        mContext,
@@ -138,10 +136,6 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
                        mMainHandler,
                        mMainChoreographer,
                        mSyncQueue);

        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
        mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
        verify(mCaptionWindowDecoration).close();
    }

@@ -149,31 +143,21 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
    public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
        final int taskId = 1;
        final ActivityManager.RunningTaskInfo taskInfo =
                createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED);
                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
        SurfaceControl surfaceControl = mock(SurfaceControl.class);
        runOnMainThread(() -> {
            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);

            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);

        verify(mCaptionWindowDecorFactory, never())
                .create(
                    mContext,
                    mDisplayController,
                    mTaskOrganizer,
                    taskInfo,
                    surfaceControl,
                    mMainHandler,
                    mMainChoreographer,
                    mSyncQueue);

            taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
            taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);

            mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);

        verify(mCaptionWindowDecorFactory)
        });
        verify(mCaptionWindowDecorFactory, times(1))
                .create(
                        mContext,
                        mDisplayController,
@@ -185,34 +169,79 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
                        mSyncQueue);
    }

    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
    @Test
    public void testCreateAndDisposeEventReceiver() throws Exception {
        final int taskId = 1;
        final ActivityManager.RunningTaskInfo taskInfo =
                createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
        taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
        runOnMainThread(() -> {
            SurfaceControl surfaceControl = mock(SurfaceControl.class);
            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);

            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);

            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
        });
        verify(mMockInputMonitorFactory).create(any(), any());
        verify(mInputMonitor).dispose();
    }

    @Test
    public void testEventReceiversOnMultipleDisplays() throws Exception {
        runOnMainThread(() -> {
            SurfaceView surfaceView = new SurfaceView(mContext);
            final DisplayManager mDm = mContext.getSystemService(DisplayManager.class);
            final VirtualDisplay secondaryDisplay = mDm.createVirtualDisplay(
                    "testEventReceiversOnMultipleDisplays", /*width=*/ 400, /*height=*/ 400,
                    /*densityDpi=*/ 320, surfaceView.getHolder().getSurface(),
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
            int secondaryDisplayId = secondaryDisplay.getDisplay().getDisplayId();

            final int taskId = 1;
            final ActivityManager.RunningTaskInfo taskInfo =
                    createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
            final ActivityManager.RunningTaskInfo secondTaskInfo =
                    createTaskInfo(taskId + 1, secondaryDisplayId, WINDOWING_MODE_FREEFORM);
            final ActivityManager.RunningTaskInfo thirdTaskInfo =
                    createTaskInfo(taskId + 2, secondaryDisplayId, WINDOWING_MODE_FREEFORM);

            SurfaceControl surfaceControl = mock(SurfaceControl.class);
            final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
            final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);

            mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
            mCaptionWindowDecorViewModel.onTaskOpening(secondTaskInfo, surfaceControl,
                    startT, finishT);
            mCaptionWindowDecorViewModel.onTaskOpening(thirdTaskInfo, surfaceControl,
                    startT, finishT);
            mCaptionWindowDecorViewModel.destroyWindowDecoration(thirdTaskInfo);
            mCaptionWindowDecorViewModel.destroyWindowDecoration(taskInfo);
        });
        verify(mMockInputMonitorFactory, times(2)).create(any(), any());
        verify(mInputMonitor, times(1)).dispose();
    }

    private void runOnMainThread(Runnable r) throws Exception {
        final Handler mainHandler = new Handler(Looper.getMainLooper());
        final CountDownLatch latch = new CountDownLatch(1);
        mainHandler.post(() -> {
            r.run();
            latch.countDown();
        });
        latch.await(20, TimeUnit.MILLISECONDS);
    }

    private static ActivityManager.RunningTaskInfo createTaskInfo(int taskId,
            int displayId, int windowingMode) {
        ActivityManager.RunningTaskInfo taskInfo =
                 new TestRunningTaskInfoBuilder()
                .setDisplayId(Display.DEFAULT_DISPLAY)
                .setDisplayId(displayId)
                .setVisible(true)
                .build();
        taskInfo.taskId = taskId;
        taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
        return taskInfo;
    }

    private static class MockObjectSupplier<T> implements Supplier<T> {
        private final List<T> mObjects;
        private final Supplier<T> mDefaultSupplier;
        private int mNumOfCalls = 0;

        private MockObjectSupplier(List<T> objects, Supplier<T> defaultSupplier) {
            mObjects = objects;
            mDefaultSupplier = defaultSupplier;
        }

        @Override
        public T get() {
            final T mock =
                    mNumOfCalls < mObjects.size() ? mObjects.get(mNumOfCalls)
                        : mDefaultSupplier.get();
            ++mNumOfCalls;
            return mock;
        }
    }
}