Merge "Add unit test for EventReceiver class in CaptionWindowDecorViewModel" into tm-qpr-dev

This commit is contained in:
Matt Sziklay
2022-12-14 17:12:31 +00:00
committed by Android (Google) Code Review
2 changed files with 141 additions and 116 deletions

View File

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

View File

@@ -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,44 +96,46 @@ 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);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
GrantPermissionRule.grant(android.Manifest.permission.MONITOR_INPUT);
runOnMainThread(() -> {
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
mCaptionWindowDecorViewModel.onTaskOpening(taskInfo, surfaceControl, startT, finishT);
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,
mDisplayController,
mTaskOrganizer,
taskInfo,
surfaceControl,
mMainHandler,
mMainChoreographer,
mSyncQueue);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
mContext,
mDisplayController,
mTaskOrganizer,
taskInfo,
surfaceControl,
mMainHandler,
mMainChoreographer,
mSyncQueue);
verify(mCaptionWindowDecoration).close();
}
@@ -149,70 +143,105 @@ 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);
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
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);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
verify(mCaptionWindowDecorFactory, never())
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
});
verify(mCaptionWindowDecorFactory, times(1))
.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)
.create(
mContext,
mDisplayController,
mTaskOrganizer,
taskInfo,
surfaceControl,
mMainHandler,
mMainChoreographer,
mSyncQueue);
mContext,
mDisplayController,
mTaskOrganizer,
taskInfo,
surfaceControl,
mMainHandler,
mMainChoreographer,
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;
}
}
}