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 androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController; 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 com.android.wm.shell.transition.Transitions;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier;
/** /**
* View model for the window decoration with a caption and shadows. Works with * 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 { public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private static final String TAG = "CaptionViewModel"; private static final String TAG = "CaptionViewModel";
private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory; private final CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
private final Supplier<InputManager> mInputManagerSupplier;
private final ActivityTaskManager mActivityTaskManager; private final ActivityTaskManager mActivityTaskManager;
private final ShellTaskOrganizer mTaskOrganizer; private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext; private final Context mContext;
@@ -82,7 +81,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory(); private InputMonitorFactory mInputMonitorFactory;
public CaptionWindowDecorViewModel( public CaptionWindowDecorViewModel(
Context context, Context context,
@@ -101,10 +100,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
syncQueue, syncQueue,
desktopModeController, desktopModeController,
new CaptionWindowDecoration.Factory(), new CaptionWindowDecoration.Factory(),
InputManager::getInstance); new InputMonitorFactory());
} }
public CaptionWindowDecorViewModel( @VisibleForTesting
CaptionWindowDecorViewModel(
Context context, Context context,
Handler mainHandler, Handler mainHandler,
Choreographer mainChoreographer, Choreographer mainChoreographer,
@@ -113,8 +113,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue, SyncTransactionQueue syncQueue,
Optional<DesktopModeController> desktopModeController, Optional<DesktopModeController> desktopModeController,
CaptionWindowDecoration.Factory captionWindowDecorFactory, CaptionWindowDecoration.Factory captionWindowDecorFactory,
Supplier<InputManager> inputManagerSupplier) { InputMonitorFactory inputMonitorFactory) {
mContext = context; mContext = context;
mMainHandler = mainHandler; mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer; mMainChoreographer = mainChoreographer;
@@ -125,11 +124,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDesktopModeController = desktopModeController; mDesktopModeController = desktopModeController;
mCaptionWindowDecorFactory = captionWindowDecorFactory; mCaptionWindowDecorFactory = captionWindowDecorFactory;
mInputManagerSupplier = inputManagerSupplier; mInputMonitorFactory = inputMonitorFactory;
}
void setEventReceiverFactory(EventReceiverFactory eventReceiverFactory) {
mEventReceiverFactory = eventReceiverFactory;
} }
@Override @Override
@@ -205,7 +200,6 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
decoration.close(); decoration.close();
int displayId = taskInfo.displayId; int displayId = taskInfo.displayId;
if (mEventReceiversByDisplay.contains(displayId)) { if (mEventReceiversByDisplay.contains(displayId)) {
EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
removeTaskFromEventReceiver(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 * 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) { private void createInputChannel(int displayId) {
InputManager inputManager = mInputManagerSupplier.get(); InputManager inputManager = InputManager.getInstance();
InputMonitor inputMonitor = InputMonitor inputMonitor =
inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId()); mInputMonitorFactory.create(inputManager, mContext);
EventReceiver eventReceiver = mEventReceiverFactory.create( EventReceiver eventReceiver = new EventReceiver(inputMonitor,
inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper()); inputMonitor.getInputChannel(), Looper.myLooper());
mEventReceiversByDisplay.put(displayId, eventReceiver); mEventReceiversByDisplay.put(displayId, eventReceiver);
} }
@@ -562,4 +550,12 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.get(taskId).closeHandleMenu(); 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 android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; 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.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -37,9 +38,9 @@ import android.view.Display;
import android.view.InputChannel; import android.view.InputChannel;
import android.view.InputMonitor; import android.view.InputMonitor;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceView;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.test.rule.GrantPermissionRule;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.ShellTestCase;
@@ -55,37 +56,28 @@ import org.mockito.Mock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** Tests of {@link CaptionWindowDecorViewModel} */ /** Tests of {@link CaptionWindowDecorViewModel} */
@SmallTest @SmallTest
public class CaptionWindowDecorViewModelTests extends ShellTestCase { 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 CaptionWindowDecoration.Factory mCaptionWindowDecorFactory;
@Mock private Handler mMainHandler; @Mock private Handler mMainHandler;
@Mock private Choreographer mMainChoreographer; @Mock private Choreographer mMainChoreographer;
@Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private DisplayController mDisplayController; @Mock private DisplayController mDisplayController;
@Mock private SyncTransactionQueue mSyncQueue; @Mock private SyncTransactionQueue mSyncQueue;
@Mock private DesktopModeController mDesktopModeController; @Mock private DesktopModeController mDesktopModeController;
@Mock private InputMonitor mInputMonitor; @Mock private InputMonitor mInputMonitor;
@Mock private InputChannel mInputChannel;
@Mock private CaptionWindowDecorViewModel.EventReceiverFactory mEventReceiverFactory;
@Mock private CaptionWindowDecorViewModel.EventReceiver mEventReceiver;
@Mock private InputManager mInputManager; @Mock private InputManager mInputManager;
@Mock private CaptionWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
private final List<InputManager> mMockInputManagers = new ArrayList<>(); private final List<InputManager> mMockInputManagers = new ArrayList<>();
private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel; private CaptionWindowDecorViewModel mCaptionWindowDecorViewModel;
@@ -104,30 +96,36 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mSyncQueue, mSyncQueue,
Optional.of(mDesktopModeController), Optional.of(mDesktopModeController),
mCaptionWindowDecorFactory, mCaptionWindowDecorFactory,
new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class))); mMockInputMonitorFactory
mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory); );
doReturn(mCaptionWindowDecoration) doReturn(mCaptionWindowDecoration)
.when(mCaptionWindowDecorFactory) .when(mCaptionWindowDecorFactory)
.create(any(), any(), any(), any(), any(), any(), any(), any()); .create(any(), any(), any(), any(), any(), any(), any(), any());
when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor); when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor);
when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver); // InputChannel cannot be mocked because it passes to InputEventReceiver.
when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel); final InputChannel[] inputChannels = InputChannel.openInputChannelPair(TAG);
inputChannels[0].dispose();
when(mInputMonitor.getInputChannel()).thenReturn(inputChannels[1]);
} }
@Test @Test
public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception { public void testDeleteCaptionOnChangeTransitionWhenNecessary() throws Exception {
Looper.prepare();
final int taskId = 1; final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo = final ActivityManager.RunningTaskInfo taskInfo =
createTaskInfo(taskId, WINDOWING_MODE_FREEFORM); createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_FREEFORM);
SurfaceControl surfaceControl = mock(SurfaceControl.class); SurfaceControl surfaceControl = mock(SurfaceControl.class);
runOnMainThread(() -> {
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = 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); 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) verify(mCaptionWindowDecorFactory)
.create( .create(
mContext, mContext,
@@ -138,10 +136,6 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mMainHandler, mMainHandler,
mMainChoreographer, mMainChoreographer,
mSyncQueue); mSyncQueue);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
verify(mCaptionWindowDecoration).close(); verify(mCaptionWindowDecoration).close();
} }
@@ -149,31 +143,21 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception { public void testCreateCaptionOnChangeTransitionWhenNecessary() throws Exception {
final int taskId = 1; final int taskId = 1;
final ActivityManager.RunningTaskInfo taskInfo = final ActivityManager.RunningTaskInfo taskInfo =
createTaskInfo(taskId, WINDOWING_MODE_UNDEFINED); createTaskInfo(taskId, Display.DEFAULT_DISPLAY, WINDOWING_MODE_UNDEFINED);
SurfaceControl surfaceControl = mock(SurfaceControl.class); SurfaceControl surfaceControl = mock(SurfaceControl.class);
runOnMainThread(() -> {
final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class); final SurfaceControl.Transaction startT = mock(SurfaceControl.Transaction.class);
final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class); final SurfaceControl.Transaction finishT = mock(SurfaceControl.Transaction.class);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT); 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.setWindowingMode(WINDOWING_MODE_FREEFORM);
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT); mCaptionWindowDecorViewModel.onTaskChanging(taskInfo, surfaceControl, startT, finishT);
});
verify(mCaptionWindowDecorFactory) verify(mCaptionWindowDecorFactory, times(1))
.create( .create(
mContext, mContext,
mDisplayController, mDisplayController,
@@ -185,34 +169,79 @@ public class CaptionWindowDecorViewModelTests extends ShellTestCase {
mSyncQueue); 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 = ActivityManager.RunningTaskInfo taskInfo =
new TestRunningTaskInfoBuilder() new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY) .setDisplayId(displayId)
.setVisible(true) .setVisible(true)
.build(); .build();
taskInfo.taskId = taskId; taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode); taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo; 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;
}
}
} }