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

Commit 9bf5601b authored by Winson Chung's avatar Winson Chung
Browse files

Enforce that app & launchable intent drags are mututally exclusive

- We should be checking that the given data is actually an app drag
  (ie. dragging an icon from taskbar) before setting app data, and
  as per comments in the field declarations, that we only set either
  appData or launchableIntent as non-null (with app-drags taking
  priority as if both are set (this behavior doesn't change in this CL)

Fixes: 411574401
Flag: EXEMPT bugfix
Test: atest WMShellUnitTests
Change-Id: I1c56c74db466021e125c500dd606eba21820ffb9
parent 00e56a25
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -133,7 +133,11 @@ public class DragSession {
        // TODO: This should technically check & respect config_supportsNonResizableMultiWindow
        dragItemSupportsSplitscreen = activityInfo == null
                || ActivityInfo.isResizeableMode(activityInfo.resizeMode);
        appData = mInitialDragData.getItemAt(0).getIntent();
        launchableIntent = DragUtils.getLaunchIntent(mInitialDragData, mInitialDragFlags);
        appData = DragUtils.isAppDrag(getClipDescription())
                ? mInitialDragData.getItemAt(0).getIntent()
                : null;
        launchableIntent = appData != null
                ? null
                : DragUtils.getLaunchIntent(mInitialDragData, mInitialDragFlags);
    }
}
+125 −25
Original line number Diff line number Diff line
@@ -20,23 +20,23 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ClipDescription
import android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID
import android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY
import android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT
import android.os.PersistableBundle
import android.os.RemoteException
import android.testing.AndroidTestingRunner
import android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub

/**
 * Tests for DragSession.
@@ -46,22 +46,15 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class DragSessionTest : ShellTestCase() {
    @Mock
    private lateinit var activityTaskManager: ActivityTaskManager

    @Mock
    private lateinit var displayLayout: DisplayLayout

    @Before
    @Throws(RemoteException::class)
    fun setUp() {
        MockitoAnnotations.initMocks(this)
    }
    private val activityTaskManager = mock<ActivityTaskManager>()
    private val displayLayout = mock<DisplayLayout>()

    @Test
    fun testNullClipData() {
        // Start a new drag session with null data
        val session = DragSession(activityTaskManager, displayLayout, null, 0)

        assertThat(session.hideDragSourceTaskId).isEqualTo(-1)
    }

@@ -71,10 +64,12 @@ class DragSessionTest : ShellTestCase() {
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }

        // Simulate dragging an app
        val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
        // Set up for dragging an app
        val data = DragTestUtils.createAppClipData(MIMETYPE_APPLICATION_SHORTCUT)

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
@@ -93,10 +88,12 @@ class DragSessionTest : ShellTestCase() {
            createTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, alwaysOnTop=true),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }

        // Simulate dragging an app
        val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
        // Set up for dragging an app
        val data = DragTestUtils.createAppClipData(MIMETYPE_APPLICATION_SHORTCUT)

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
@@ -115,10 +112,12 @@ class DragSessionTest : ShellTestCase() {
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }

        // Simulate dragging an app with hide-drag-source set for the second (top most) app
        val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
        // Set up for dragging an app
        val data = DragTestUtils.createAppClipData(MIMETYPE_APPLICATION_SHORTCUT)
        data.description.extras =
            PersistableBundle().apply {
                putInt(
@@ -133,4 +132,105 @@ class DragSessionTest : ShellTestCase() {

        assertThat(session.hideDragSourceTaskId).isEqualTo(runningTasks.last().taskId)
    }

    @Test
    fun testAppData() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }

        // Set up for dragging with app data
        val data = DragTestUtils.createAppClipData(MIMETYPE_APPLICATION_ACTIVITY)

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
        session.initialize(true /* skipUpdateRunningTask */)

        assertThat(session.appData).isNotNull()
        assertThat(session.launchableIntent).isNull()
    }

    @Test
    fun testLaunchableIntent() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }
        val pendingIntent = DragTestUtils.createLaunchableIntent(mContext)

        // Set up for dragging with a launchable intent
        val data = DragTestUtils.createIntentClipData(pendingIntent)

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data,
            DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG)
        session.initialize(true /* skipUpdateRunningTask */)

        assertThat(session.appData).isNull()
        assertThat(session.launchableIntent).isNotNull()
    }

    @Test
    fun testBothValidAppDataAndLaunchableIntent_launchableIntentIsNull() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }
        val pendingIntent = DragTestUtils.createLaunchableIntent(mContext)

        // Set up for dragging data with both and app-data intent and a laucnhable intent
        val launchData = DragTestUtils.createIntentClipData(pendingIntent)
        val data = DragTestUtils.createAppClipData(MIMETYPE_APPLICATION_ACTIVITY)
        data.addItem(launchData.getItemAt(0))

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data,
            DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG)
        session.initialize(true /* skipUpdateRunningTask */)

        // Since the app data is valid, we prioritize using that
        assertThat(session.appData).isNotNull()
        assertThat(session.launchableIntent).isNull()
    }

    @Test
    fun testInvalidAppDataWithValidLaunchableIntent_appDataIsNull() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        activityTaskManager.stub {
            on { getTasks(any(), any()) } doReturn runningTasks
        }
        val pendingIntent = DragTestUtils.createLaunchableIntent(mContext)

        // Set up for dragging data with an unknown mime type, this should not be treated as a valid
        // app drag
        val launchData = DragTestUtils.createIntentClipData(pendingIntent)
        val data = DragTestUtils.createAppClipData("unknown_mime_type")
        data.addItem(launchData.getItemAt(0))

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data,
            DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG)
        session.initialize(true /* skipUpdateRunningTask */)

        // Since the app data is invalid, we use the launchable intent
        assertThat(session.appData).isNull()
        assertThat(session.launchableIntent).isNotNull()
    }
}
+14 −1
Original line number Diff line number Diff line
@@ -17,15 +17,17 @@ package com.android.wm.shell.draganddrop

import android.app.ActivityManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
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.pm.ApplicationInfo
import android.os.Process
import java.util.Random
import org.mockito.Mockito
import java.util.Random

/**
 * Convenience methods for drag tests.
@@ -79,6 +81,17 @@ object DragTestUtils {
        return data
    }

    /**
     * Creates a new launchable intent.
     */
    @JvmStatic
    fun createLaunchableIntent(context: Context) : PendingIntent {
        val fakeActivityIntent = Intent()
        fakeActivityIntent.component = ComponentName("com.android.wm.shell.draganddrop",
            "FakeActivity")
        return PendingIntent.getActivity(context, 0, fakeActivityIntent, FLAG_IMMUTABLE)
    }

    /**
     * Sets the given clip data to be resizeable.
     */
+24 −51
Original line number Diff line number Diff line
@@ -20,15 +20,11 @@ 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 android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;
import static com.android.wm.shell.draganddrop.DragTestUtils.createIntentClipData;
import static com.android.wm.shell.draganddrop.DragTestUtils.createTaskInfo;
import static com.android.wm.shell.draganddrop.DragTestUtils.setClipDataResizeable;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -50,8 +46,6 @@ 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 static org.mockito.Mockito.when;
import static org.mockito.quality.Strictness.LENIENT;

import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -73,7 +67,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.SplitDragPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -112,13 +105,6 @@ public class SplitDragPolicyTest extends ShellTestCase {
    private Insets mInsets;
    private SplitDragPolicy mPolicy;

    private ClipData mActivityClipData;
    private PendingIntent mLaunchableIntentPendingIntent;
    private ClipData mLaunchableIntentClipData;
    private ClipData mNonResizeableActivityClipData;
    private ClipData mTaskClipData;
    private ClipData mShortcutClipData;

    private ActivityManager.RunningTaskInfo mHomeTask;
    private ActivityManager.RunningTaskInfo mFullscreenAppTask;
    private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
@@ -128,11 +114,6 @@ public class SplitDragPolicyTest extends ShellTestCase {
    @Before
    public void setUp() throws RemoteException {
        MockitoAnnotations.initMocks(this);
        mMockitoSession = mockitoSession()
                .strictness(LENIENT)
                .mockStatic(DragUtils.class)
                .startMocking();
        when(DragUtils.canHandleDrag(any())).thenReturn(true);

        Resources res = mock(Resources.class);
        Configuration config = new Configuration();
@@ -150,15 +131,6 @@ public class SplitDragPolicyTest extends ShellTestCase {

        mPolicy = spy(new SplitDragPolicy(mContext, mSplitScreenStarter, mFullscreenStarter,
                mock(DragZoneAnimator.class)));
        mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        mLaunchableIntentPendingIntent = mock(PendingIntent.class);
        when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
                .thenReturn(android.os.Process.myUserHandle());
        mLaunchableIntentClipData = createIntentClipData(mLaunchableIntentPendingIntent);
        mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        setClipDataResizeable(mNonResizeableActivityClipData, false);
        mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK);
        mShortcutClipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);

        mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
        mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -169,11 +141,6 @@ public class SplitDragPolicyTest extends ShellTestCase {
        setRunningTask(mFullscreenAppTask);
    }

    @After
    public void tearDown() {
        mMockitoSession.finishMocking();
    }

    private void setRunningTask(ActivityManager.RunningTaskInfo task) {
        doReturn(Collections.singletonList(task)).when(mActivityTaskManager)
                .getTasks(anyInt(), anyBoolean());
@@ -181,45 +148,48 @@ public class SplitDragPolicyTest extends ShellTestCase {

    @Test
    public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
        dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);
        final ClipData data = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        dragOverFullscreenHome_expectOnlyFullscreenTarget(data);
    }

    @Test
    public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
        dragOverFullscreenApp_expectSplitScreenTargets(mActivityClipData);
        final ClipData data = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        dragOverFullscreenApp_expectSplitScreenTargets(data);
    }

    @Test
    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mActivityClipData);
        final ClipData data = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(data);
    }

    @Test
    public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() {
        when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
                mLaunchableIntentPendingIntent);
        dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData);
        final PendingIntent pendingIntent = DragTestUtils.createLaunchableIntent(super.mContext);
        final ClipData data = DragTestUtils.createIntentClipData(pendingIntent);
        dragOverFullscreenHome_expectOnlyFullscreenTarget(data);
    }

    @Test
    public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() {
        when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
                mLaunchableIntentPendingIntent);
        dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData);
        final PendingIntent pendingIntent = DragTestUtils.createLaunchableIntent(super.mContext);
        final ClipData data = DragTestUtils.createIntentClipData(pendingIntent);
        dragOverFullscreenApp_expectSplitScreenTargets(data);
    }

    @Test
    public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
        when(DragUtils.getLaunchIntent((ClipData) any(), anyInt())).thenReturn(
                mLaunchableIntentPendingIntent);
        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData);
        final PendingIntent pendingIntent = DragTestUtils.createLaunchableIntent(super.mContext);
        final ClipData data = DragTestUtils.createIntentClipData(pendingIntent);
        dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(data);
    }

    private void dragOverFullscreenHome_expectOnlyFullscreenTarget(ClipData data) {
        doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
        setRunningTask(mHomeTask);
        DragSession dragSession = new DragSession(mActivityTaskManager,
                mLandscapeDisplayLayout, data, 0 /* dragFlags */);
                mLandscapeDisplayLayout, data, DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG);
        dragSession.initialize(false /* skipUpdateRunningTask */);
        mPolicy.start(dragSession, mLoggerSessionId);
        ArrayList<Target> targets = assertExactTargetTypes(
@@ -234,7 +204,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
        doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
        setRunningTask(mFullscreenAppTask);
        DragSession dragSession = new DragSession(mActivityTaskManager,
                mLandscapeDisplayLayout, data, 0 /* dragFlags */);
                mLandscapeDisplayLayout, data, DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG);
        dragSession.initialize(false /* skipUpdateRunningTask */);
        mPolicy.start(dragSession, mLoggerSessionId);
        ArrayList<Target> targets = assertExactTargetTypes(
@@ -254,7 +224,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
        doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
        setRunningTask(mFullscreenAppTask);
        DragSession dragSession = new DragSession(mActivityTaskManager,
                mPortraitDisplayLayout, data, 0 /* dragFlags */);
                mPortraitDisplayLayout, data, DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG);
        dragSession.initialize(false /* skipUpdateRunningTask */);
        mPolicy.start(dragSession, mLoggerSessionId);
        ArrayList<Target> targets = assertExactTargetTypes(
@@ -273,9 +243,10 @@ public class SplitDragPolicyTest extends ShellTestCase {

    @Test
    public void testTargetHitRects() {
        final ClipData data = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
        setRunningTask(mFullscreenAppTask);
        DragSession dragSession = new DragSession(mActivityTaskManager,
                mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */);
                mLandscapeDisplayLayout, data, 0 /* dragFlags */);
        dragSession.initialize(false /* skipUpdateRunningTask */);
        mPolicy.start(dragSession, mLoggerSessionId);
        ArrayList<Target> targets = mPolicy.getTargets(mInsets);
@@ -291,7 +262,9 @@ public class SplitDragPolicyTest extends ShellTestCase {

    @Test
    public void testDisallowLaunchIntentWithoutDelegationFlag() {
        assertTrue(DragUtils.getLaunchIntent(mLaunchableIntentClipData, 0) == null);
        final PendingIntent pendingIntent = DragTestUtils.createLaunchableIntent(super.mContext);
        final ClipData data = DragTestUtils.createIntentClipData(pendingIntent);
        assertTrue(DragUtils.getLaunchIntent(data, 0) == null);
    }

    private Target filterTargetByType(ArrayList<Target> targets, int type) {