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

Commit 2ead0928 authored by Winson Chung's avatar Winson Chung
Browse files

Fix issue with floating tasks being tracked as a drag running task

- Ignore floating tasks when calculating the "running" task for
  drag and drop purposes (since it will be used to determine the
  drag split layout, which isn't affected by floating tasks). Until
  we have a proper task repository/visible task tracking, just fetch
  a few more tasks to ensure that we actually resolve a non-floating
  task.

Fixes: 370659212
Flag: EXEMPT bugfix
Test: atest WMShellUnitTests
Change-Id: Ie9588c66a8687f8a4a5185e17f63dbe303322c54
parent 859bdacb
Loading
Loading
Loading
Loading
+22 −16
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.wm.shell.draganddrop;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.ClipDescription.EXTRA_HIDE_DRAG_SOURCE_TASK_ID;

@@ -94,10 +95,8 @@ public class DragSession {
    void updateRunningTask() {
        final boolean hideDragSourceTask = hideDragSourceTaskId != -1;
        final List<ActivityManager.RunningTaskInfo> tasks =
                mActivityTaskManager.getTasks(hideDragSourceTask ? 2 : 1,
                        false /* filterOnlyVisibleRecents */);
        if (!tasks.isEmpty()) {
            for (int i = tasks.size() - 1; i >= 0; i--) {
                mActivityTaskManager.getTasks(5, false /* filterOnlyVisibleRecents */);
        for (int i = 0; i < tasks.size(); i++) {
            final ActivityManager.RunningTaskInfo task = tasks.get(i);
            if (hideDragSourceTask && hideDragSourceTaskId == task.taskId) {
                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
@@ -105,6 +104,14 @@ public class DragSession {
                        task.baseIntent != null ? task.baseIntent.getComponent() : "null");
                continue;
            }
            if (!task.isVisible) {
                // Skip invisible tasks
                continue;
            }
            if (task.configuration.windowConfiguration.isAlwaysOnTop()) {
                // Skip always-on-top floating tasks
                continue;
            }
            runningTaskInfo = task;
            runningTaskWinMode = task.getWindowingMode();
            runningTaskActType = task.getActivityType();
@@ -114,7 +121,6 @@ public class DragSession {
            break;
        }
    }
    }

    /**
     * Updates the session data based on the current state of the system at the start of the drag.
+3 −14
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_STARTED;

import static com.android.wm.shell.draganddrop.DragTestUtils.createAppClipData;

import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
@@ -30,9 +32,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
import android.view.Display;
import android.view.DragEvent;
@@ -128,7 +128,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
        doReturn(display).when(dragLayout).getDisplay();
        doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();

        final ClipData clipData = createClipData();
        final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
        final DragEvent event = mock(DragEvent.class);
        doReturn(ACTION_DRAG_STARTED).when(event).getAction();
        doReturn(clipData).when(event).getClipData();
@@ -150,15 +150,4 @@ public class DragAndDropControllerTest extends ShellTestCase {
        mController.onDrag(dragLayout, event);
        verify(mDragAndDropListener, never()).onDragStarted();
    }

    private ClipData createClipData() {
        ClipDescription clipDescription = new ClipDescription(MIMETYPE_APPLICATION_SHORTCUT,
                new String[] { MIMETYPE_APPLICATION_SHORTCUT });
        Intent i = new Intent();
        i.putExtra(Intent.EXTRA_PACKAGE_NAME, "pkg");
        i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcutId");
        i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
        ClipData.Item item = new ClipData.Item(i);
        return new ClipData(clipDescription, item);
    }
}
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.app.ActivityTaskManager
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.os.PersistableBundle
import android.os.RemoteException
import android.testing.AndroidTestingRunner
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

/**
 * Tests for DragSession.
 *
 * Usage: atest WMShellUnitTests:DragSessionTest
 */
@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)
    }

    @Test
    fun testGetRunningTask() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)

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

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
        session.updateRunningTask()

        assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first())
        assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode)
        assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType)
    }

    @Test
    fun testGetRunningTaskWithFloatingTasks() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, alwaysOnTop=true),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)

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

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
        session.updateRunningTask()

        // Ensure that we find the first non-floating task
        assertThat(session.runningTaskInfo).isEqualTo(runningTasks.first())
        assertThat(session.runningTaskWinMode).isEqualTo(runningTasks.first().windowingMode)
        assertThat(session.runningTaskActType).isEqualTo(runningTasks.first().activityType)
    }

    @Test
    fun testHideDragSource_readDragFlag() {
        // Set up running tasks
        val runningTasks = listOf(
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
            createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD),
        )
        whenever(activityTaskManager.getTasks(any(), any())).thenReturn(runningTasks)

        // Simulate dragging an app with hide-drag-source set for the second (top most) app
        val data = DragTestUtils.createAppClipData(ClipDescription.MIMETYPE_APPLICATION_SHORTCUT)
        data.description.extras =
            PersistableBundle().apply {
                putInt(
                    EXTRA_HIDE_DRAG_SOURCE_TASK_ID,
                    runningTasks.last().taskId
                )
            }

        // Start a new drag session
        val session = DragSession(activityTaskManager, displayLayout, data, 0)
        session.updateRunningTask()

        assertThat(session.hideDragSourceTaskId).isEqualTo(runningTasks.last().taskId)
    }
}
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.app.ActivityManager
import android.app.PendingIntent
import android.content.ClipData
import android.content.ClipDescription
import android.content.ComponentName
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

/**
 * Convenience methods for drag tests.
 */
object DragTestUtils {
    /**
     * Creates an app-based clip data that is by default resizeable.
     */
    @JvmStatic
    fun createAppClipData(mimeType: String): ClipData {
        val clipDescription = ClipDescription(mimeType, arrayOf(mimeType))
        val i = Intent()
        when (mimeType) {
            ClipDescription.MIMETYPE_APPLICATION_SHORTCUT -> {
                i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package")
                i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id")
            }

            ClipDescription.MIMETYPE_APPLICATION_TASK -> i.putExtra(Intent.EXTRA_TASK_ID, 12345)
            ClipDescription.MIMETYPE_APPLICATION_ACTIVITY -> {
                val pi = Mockito.mock(PendingIntent::class.java)
                Mockito.doReturn(Process.myUserHandle()).`when`(pi).creatorUserHandle
                i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi)
            }
        }
        i.putExtra(Intent.EXTRA_USER, Process.myUserHandle())
        val item = ClipData.Item(i)
        item.activityInfo = ActivityInfo()
        item.activityInfo.applicationInfo = ApplicationInfo()
        val data = ClipData(clipDescription, item)
        setClipDataResizeable(data, true)
        return data
    }

    /**
     * Creates an intent-based clip data that is by default resizeable.
     */
    @JvmStatic
    fun createIntentClipData(intent: PendingIntent): ClipData {
        val clipDescription = ClipDescription(
            "Intent",
            arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT)
        )
        val item = ClipData.Item.Builder()
            .setIntentSender(intent.intentSender)
            .build()
        item.activityInfo = ActivityInfo()
        item.activityInfo.applicationInfo = ApplicationInfo()
        val data = ClipData(clipDescription, item)
        setClipDataResizeable(data, true)
        return data
    }

    /**
     * Sets the given clip data to be resizeable.
     */
    @JvmStatic
    fun setClipDataResizeable(data: ClipData, resizeable: Boolean) {
        data.getItemAt(0).activityInfo.resizeMode = if (resizeable)
            ActivityInfo.RESIZE_MODE_RESIZEABLE
        else
            ActivityInfo.RESIZE_MODE_UNRESIZEABLE
    }

    /**
     * Creates a task info with the given params.
     */
    @JvmStatic
    fun createTaskInfo(winMode: Int, actType: Int): ActivityManager.RunningTaskInfo {
        return createTaskInfo(winMode, actType, false)
    }

    /**
     * Creates a task info with the given params.
     */
    @JvmStatic
    fun createTaskInfo(winMode: Int, actType: Int, alwaysOnTop: Boolean = false):
            ActivityManager.RunningTaskInfo {
        val info = ActivityManager.RunningTaskInfo()
        info.taskId = Random().nextInt()
        info.configuration.windowConfiguration.activityType = actType
        info.configuration.windowConfiguration.windowingMode = winMode
        info.configuration.windowConfiguration.isAlwaysOnTop = alwaysOnTop
        info.isVisible = true
        info.isResizeable = true
        info.baseActivity = ComponentName(
            "com.android.wm.shell",
            ".ActivityWithMode$winMode"
        )
        info.baseIntent = Intent()
        info.baseIntent.setComponent(info.baseActivity)
        val activityInfo = ActivityInfo()
        activityInfo.packageName = info.baseActivity!!.packageName
        activityInfo.name = info.baseActivity!!.className
        info.topActivityInfo = activityInfo
        return info
    }
}
+4 −70
Original line number Diff line number Diff line
@@ -22,11 +22,12 @@ 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.content.ClipDescription.MIMETYPE_TEXT_INTENT;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
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;
@@ -55,11 +56,7 @@ 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;
@@ -176,74 +173,11 @@ public class SplitDragPolicyTest extends ShellTestCase {
        mMockitoSession.finishMocking();
    }

    /**
     * Creates an app-based clip data that is by default resizeable.
     */
    private ClipData createAppClipData(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:
                final PendingIntent pi = mock(PendingIntent.class);
                doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle();
                i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi);
                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;
    }

    /**
     * Creates an intent-based clip data that is by default resizeable.
     */
    private ClipData createIntentClipData(PendingIntent intent) {
        ClipDescription clipDescription = new ClipDescription("Intent",
                new String[] { MIMETYPE_TEXT_INTENT });
        ClipData.Item item = new ClipData.Item.Builder()
                .setIntentSender(intent.getIntentSender())
                .build();
        ClipData data = new ClipData(clipDescription, item);
        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() {
        dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);