| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.wm.shell.draganddrop; |
| |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; |
| import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; |
| import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; |
| import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; |
| import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; |
| |
| import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; |
| import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; |
| import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; |
| import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; |
| import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; |
| import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; |
| import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; |
| import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP; |
| |
| import static junit.framework.Assert.assertTrue; |
| import static junit.framework.Assert.fail; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.verify; |
| |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.PendingIntent; |
| import android.content.ClipData; |
| import android.content.ClipDescription; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.graphics.Insets; |
| import android.os.RemoteException; |
| import android.view.DisplayInfo; |
| |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.internal.logging.InstanceId; |
| import com.android.wm.shell.ShellTestCase; |
| import com.android.wm.shell.common.DisplayLayout; |
| import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; |
| import com.android.wm.shell.splitscreen.SplitScreenController; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| |
| /** |
| * Tests for the drag and drop policy. |
| */ |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| public class DragAndDropPolicyTest extends ShellTestCase { |
| |
| @Mock |
| private Context mContext; |
| |
| @Mock |
| private ActivityTaskManager mActivityTaskManager; |
| |
| // Both the split-screen and start interface. |
| @Mock |
| private SplitScreenController mSplitScreenStarter; |
| |
| @Mock |
| private InstanceId mLoggerSessionId; |
| |
| private DisplayLayout mLandscapeDisplayLayout; |
| private DisplayLayout mPortraitDisplayLayout; |
| private Insets mInsets; |
| private DragAndDropPolicy mPolicy; |
| |
| private ClipData mActivityClipData; |
| private ClipData mNonResizeableActivityClipData; |
| private ClipData mTaskClipData; |
| private ClipData mShortcutClipData; |
| |
| private ActivityManager.RunningTaskInfo mHomeTask; |
| private ActivityManager.RunningTaskInfo mFullscreenAppTask; |
| private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask; |
| |
| @Before |
| public void setUp() throws RemoteException { |
| MockitoAnnotations.initMocks(this); |
| |
| Resources res = mock(Resources.class); |
| Configuration config = new Configuration(); |
| doReturn(config).when(res).getConfiguration(); |
| doReturn(res).when(mContext).getResources(); |
| DisplayInfo info = new DisplayInfo(); |
| info.logicalWidth = 200; |
| info.logicalHeight = 100; |
| mLandscapeDisplayLayout = new DisplayLayout(info, res, false, false); |
| DisplayInfo info2 = new DisplayInfo(); |
| info.logicalWidth = 100; |
| info.logicalHeight = 200; |
| mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false); |
| mInsets = Insets.of(0, 0, 0, 0); |
| |
| mPolicy = spy(new DragAndDropPolicy( |
| mContext, mActivityTaskManager, mSplitScreenStarter, mSplitScreenStarter)); |
| mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); |
| mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); |
| setClipDataResizeable(mNonResizeableActivityClipData, false); |
| mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK); |
| mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT); |
| |
| mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME); |
| mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); |
| mNonResizeableFullscreenAppTask = |
| createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); |
| mNonResizeableFullscreenAppTask.isResizeable = false; |
| |
| setRunningTask(mFullscreenAppTask); |
| } |
| |
| /** |
| * Creates a clip data that is by default resizeable. |
| */ |
| private ClipData createClipData(String mimeType) { |
| ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType }); |
| Intent i = new Intent(); |
| switch (mimeType) { |
| case MIMETYPE_APPLICATION_SHORTCUT: |
| i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package"); |
| i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id"); |
| break; |
| case MIMETYPE_APPLICATION_TASK: |
| i.putExtra(Intent.EXTRA_TASK_ID, 12345); |
| break; |
| case MIMETYPE_APPLICATION_ACTIVITY: |
| i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class)); |
| break; |
| } |
| i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); |
| ClipData.Item item = new ClipData.Item(i); |
| item.setActivityInfo(new ActivityInfo()); |
| ClipData data = new ClipData(clipDescription, item); |
| setClipDataResizeable(data, true); |
| return data; |
| } |
| |
| private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) { |
| ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); |
| info.configuration.windowConfiguration.setActivityType(actType); |
| info.configuration.windowConfiguration.setWindowingMode(winMode); |
| info.isResizeable = true; |
| info.baseActivity = new ComponentName(getInstrumentation().getContext(), |
| ".ActivityWithMode" + winMode); |
| info.baseIntent = new Intent(); |
| info.baseIntent.setComponent(info.baseActivity); |
| ActivityInfo activityInfo = new ActivityInfo(); |
| activityInfo.packageName = info.baseActivity.getPackageName(); |
| activityInfo.name = info.baseActivity.getClassName(); |
| info.topActivityInfo = activityInfo; |
| return info; |
| } |
| |
| private void setRunningTask(ActivityManager.RunningTaskInfo task) { |
| doReturn(Collections.singletonList(task)).when(mActivityTaskManager) |
| .getTasks(anyInt(), anyBoolean()); |
| } |
| |
| private void setClipDataResizeable(ClipData data, boolean resizeable) { |
| data.getItemAt(0).getActivityInfo().resizeMode = resizeable |
| ? ActivityInfo.RESIZE_MODE_RESIZEABLE |
| : ActivityInfo.RESIZE_MODE_UNRESIZEABLE; |
| } |
| |
| @Test |
| public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() { |
| setRunningTask(mHomeTask); |
| mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); |
| ArrayList<Target> targets = assertExactTargetTypes( |
| mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); |
| |
| mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData); |
| verify(mSplitScreenStarter).startIntent(any(), any(), |
| eq(SPLIT_POSITION_UNDEFINED), any()); |
| } |
| |
| @Test |
| public void testDragAppOverFullscreenApp_expectSplitScreenTargets() { |
| setRunningTask(mFullscreenAppTask); |
| mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); |
| ArrayList<Target> targets = assertExactTargetTypes( |
| mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); |
| |
| mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData); |
| verify(mSplitScreenStarter).startIntent(any(), any(), |
| eq(SPLIT_POSITION_TOP_OR_LEFT), any()); |
| reset(mSplitScreenStarter); |
| |
| mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData); |
| verify(mSplitScreenStarter).startIntent(any(), any(), |
| eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); |
| } |
| |
| @Test |
| public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() { |
| setRunningTask(mFullscreenAppTask); |
| mPolicy.start(mPortraitDisplayLayout, mActivityClipData, mLoggerSessionId); |
| ArrayList<Target> targets = assertExactTargetTypes( |
| mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); |
| |
| mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData); |
| verify(mSplitScreenStarter).startIntent(any(), any(), |
| eq(SPLIT_POSITION_TOP_OR_LEFT), any()); |
| reset(mSplitScreenStarter); |
| |
| mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData); |
| verify(mSplitScreenStarter).startIntent(any(), any(), |
| eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any()); |
| } |
| |
| @Test |
| public void testTargetHitRects() { |
| setRunningTask(mFullscreenAppTask); |
| mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId); |
| ArrayList<Target> targets = mPolicy.getTargets(mInsets); |
| for (Target t : targets) { |
| assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t); |
| assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.right - 1, t.hitRegion.top) == t); |
| assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.right - 1, t.hitRegion.bottom - 1) |
| == t); |
| assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.bottom - 1) |
| == t); |
| } |
| } |
| |
| private Target filterTargetByType(ArrayList<Target> targets, int type) { |
| for (Target t : targets) { |
| if (type == t.type) { |
| return t; |
| } |
| } |
| fail("Target with type: " + type + " not found"); |
| return null; |
| } |
| |
| private ArrayList<Target> assertExactTargetTypes(ArrayList<Target> targets, |
| int... expectedTargetTypes) { |
| HashSet<Integer> expected = new HashSet<>(); |
| for (int t : expectedTargetTypes) { |
| expected.add(t); |
| } |
| for (Target t : targets) { |
| if (!expected.contains(t.type)) { |
| fail("Found unexpected target type: " + t.type); |
| } |
| expected.remove(t.type); |
| } |
| assertTrue(expected.isEmpty()); |
| return targets; |
| } |
| } |