Merge "[Partial Screensharing] Implement loading of recent tasks, thumbnails and icons" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
8504f8c843
@@ -484,12 +484,14 @@ public abstract class WMShellBaseModule {
|
|||||||
ShellInit shellInit,
|
ShellInit shellInit,
|
||||||
ShellCommandHandler shellCommandHandler,
|
ShellCommandHandler shellCommandHandler,
|
||||||
TaskStackListenerImpl taskStackListener,
|
TaskStackListenerImpl taskStackListener,
|
||||||
|
ActivityTaskManager activityTaskManager,
|
||||||
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
||||||
@ShellMainThread ShellExecutor mainExecutor
|
@ShellMainThread ShellExecutor mainExecutor
|
||||||
) {
|
) {
|
||||||
return Optional.ofNullable(
|
return Optional.ofNullable(
|
||||||
RecentTasksController.create(context, shellInit, shellCommandHandler,
|
RecentTasksController.create(context, shellInit, shellCommandHandler,
|
||||||
taskStackListener, desktopModeTaskRepository, mainExecutor));
|
taskStackListener, activityTaskManager, desktopModeTaskRepository,
|
||||||
|
mainExecutor));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -17,6 +17,11 @@
|
|||||||
package com.android.wm.shell.recents;
|
package com.android.wm.shell.recents;
|
||||||
|
|
||||||
import com.android.wm.shell.common.annotations.ExternalThread;
|
import com.android.wm.shell.common.annotations.ExternalThread;
|
||||||
|
import com.android.wm.shell.util.GroupedRecentTaskInfo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for interacting with the recent tasks.
|
* Interface for interacting with the recent tasks.
|
||||||
@@ -29,4 +34,11 @@ public interface RecentTasks {
|
|||||||
default IRecentTasks createExternalInterface() {
|
default IRecentTasks createExternalInterface() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the set of recent tasks.
|
||||||
|
*/
|
||||||
|
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
|
||||||
|
Consumer<List<GroupedRecentTaskInfo>> callback) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the recent task list from the system, caching it as necessary.
|
* Manages the recent task list from the system, caching it as necessary.
|
||||||
@@ -72,6 +74,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
private final ShellExecutor mMainExecutor;
|
private final ShellExecutor mMainExecutor;
|
||||||
private final TaskStackListenerImpl mTaskStackListener;
|
private final TaskStackListenerImpl mTaskStackListener;
|
||||||
private final RecentTasks mImpl = new RecentTasksImpl();
|
private final RecentTasks mImpl = new RecentTasksImpl();
|
||||||
|
private final ActivityTaskManager mActivityTaskManager;
|
||||||
private IRecentTasksListener mListener;
|
private IRecentTasksListener mListener;
|
||||||
private final boolean mIsDesktopMode;
|
private final boolean mIsDesktopMode;
|
||||||
|
|
||||||
@@ -96,6 +99,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
ShellInit shellInit,
|
ShellInit shellInit,
|
||||||
ShellCommandHandler shellCommandHandler,
|
ShellCommandHandler shellCommandHandler,
|
||||||
TaskStackListenerImpl taskStackListener,
|
TaskStackListenerImpl taskStackListener,
|
||||||
|
ActivityTaskManager activityTaskManager,
|
||||||
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
||||||
@ShellMainThread ShellExecutor mainExecutor
|
@ShellMainThread ShellExecutor mainExecutor
|
||||||
) {
|
) {
|
||||||
@@ -103,17 +107,19 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
|
return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
|
||||||
desktopModeTaskRepository, mainExecutor);
|
activityTaskManager, desktopModeTaskRepository, mainExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecentTasksController(Context context,
|
RecentTasksController(Context context,
|
||||||
ShellInit shellInit,
|
ShellInit shellInit,
|
||||||
ShellCommandHandler shellCommandHandler,
|
ShellCommandHandler shellCommandHandler,
|
||||||
TaskStackListenerImpl taskStackListener,
|
TaskStackListenerImpl taskStackListener,
|
||||||
|
ActivityTaskManager activityTaskManager,
|
||||||
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
|
||||||
ShellExecutor mainExecutor) {
|
ShellExecutor mainExecutor) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mShellCommandHandler = shellCommandHandler;
|
mShellCommandHandler = shellCommandHandler;
|
||||||
|
mActivityTaskManager = activityTaskManager;
|
||||||
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
|
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
|
||||||
mTaskStackListener = taskStackListener;
|
mTaskStackListener = taskStackListener;
|
||||||
mDesktopModeTaskRepository = desktopModeTaskRepository;
|
mDesktopModeTaskRepository = desktopModeTaskRepository;
|
||||||
@@ -269,16 +275,11 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
mListener = null;
|
mListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
List<ActivityManager.RecentTaskInfo> getRawRecentTasks(int maxNum, int flags, int userId) {
|
|
||||||
return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
|
ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
|
||||||
// Note: the returned task list is from the most-recent to least-recent order
|
// Note: the returned task list is from the most-recent to least-recent order
|
||||||
final List<ActivityManager.RecentTaskInfo> rawList = getRawRecentTasks(maxNum, flags,
|
final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
|
||||||
userId);
|
maxNum, flags, userId);
|
||||||
|
|
||||||
// Make a mapping of task id -> task info
|
// Make a mapping of task id -> task info
|
||||||
final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
|
final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
|
||||||
@@ -335,8 +336,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
if (componentName == null) {
|
if (componentName == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE,
|
List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
|
||||||
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
|
Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
|
||||||
|
ActivityManager.getCurrentUser());
|
||||||
for (int i = 0; i < tasks.size(); i++) {
|
for (int i = 0; i < tasks.size(); i++) {
|
||||||
final ActivityManager.RecentTaskInfo task = tasks.get(i);
|
final ActivityManager.RecentTaskInfo task = tasks.get(i);
|
||||||
if (task.isVisible) {
|
if (task.isVisible) {
|
||||||
@@ -374,6 +376,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
|
|||||||
mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
|
mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
|
||||||
return mIRecentTasks;
|
return mIRecentTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
|
||||||
|
Consumer<List<GroupedRecentTaskInfo>> callback) {
|
||||||
|
mMainExecutor.execute(() -> {
|
||||||
|
List<GroupedRecentTaskInfo> tasks =
|
||||||
|
RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
|
||||||
|
executor.execute(() -> callback.accept(tasks));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.when;
|
|||||||
import static java.lang.Integer.MAX_VALUE;
|
import static java.lang.Integer.MAX_VALUE;
|
||||||
|
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
|
import android.app.ActivityTaskManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
@@ -52,7 +53,6 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
|
|||||||
import com.android.wm.shell.ShellTaskOrganizer;
|
import com.android.wm.shell.ShellTaskOrganizer;
|
||||||
import com.android.wm.shell.ShellTestCase;
|
import com.android.wm.shell.ShellTestCase;
|
||||||
import com.android.wm.shell.TestShellExecutor;
|
import com.android.wm.shell.TestShellExecutor;
|
||||||
import com.android.wm.shell.common.ShellExecutor;
|
|
||||||
import com.android.wm.shell.common.TaskStackListenerImpl;
|
import com.android.wm.shell.common.TaskStackListenerImpl;
|
||||||
import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
||||||
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
|
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
|
||||||
@@ -68,7 +68,9 @@ import org.mockito.Mock;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link RecentTasksController}.
|
* Tests for {@link RecentTasksController}.
|
||||||
@@ -85,11 +87,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
|
|||||||
private ShellCommandHandler mShellCommandHandler;
|
private ShellCommandHandler mShellCommandHandler;
|
||||||
@Mock
|
@Mock
|
||||||
private DesktopModeTaskRepository mDesktopModeTaskRepository;
|
private DesktopModeTaskRepository mDesktopModeTaskRepository;
|
||||||
|
@Mock
|
||||||
|
private ActivityTaskManager mActivityTaskManager;
|
||||||
|
|
||||||
private ShellTaskOrganizer mShellTaskOrganizer;
|
private ShellTaskOrganizer mShellTaskOrganizer;
|
||||||
private RecentTasksController mRecentTasksController;
|
private RecentTasksController mRecentTasksController;
|
||||||
private ShellInit mShellInit;
|
private ShellInit mShellInit;
|
||||||
private ShellExecutor mMainExecutor;
|
private TestShellExecutor mMainExecutor;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -97,8 +101,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
|
|||||||
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
|
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
|
||||||
mShellInit = spy(new ShellInit(mMainExecutor));
|
mShellInit = spy(new ShellInit(mMainExecutor));
|
||||||
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
|
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
|
||||||
mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
|
mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
|
||||||
mMainExecutor));
|
Optional.of(mDesktopModeTaskRepository), mMainExecutor));
|
||||||
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
|
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
|
||||||
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
|
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
|
||||||
mMainExecutor);
|
mMainExecutor);
|
||||||
@@ -187,6 +191,37 @@ public class RecentTasksControllerTest extends ShellTestCase {
|
|||||||
t6.taskId, -1);
|
t6.taskId, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
|
||||||
|
Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
|
||||||
|
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
|
||||||
|
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
|
||||||
|
ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
|
||||||
|
ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
|
||||||
|
ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
|
||||||
|
ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
|
||||||
|
setRawList(t1, t2, t3, t4, t5, t6);
|
||||||
|
|
||||||
|
// Mark a couple pairs [t2, t4], [t3, t5]
|
||||||
|
SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
|
||||||
|
SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
|
||||||
|
|
||||||
|
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
|
||||||
|
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
|
||||||
|
|
||||||
|
mRecentTasksController.asRecentTasks()
|
||||||
|
.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
|
||||||
|
mMainExecutor.flushAll();
|
||||||
|
|
||||||
|
assertGroupedTasksListEquals(recentTasks[0],
|
||||||
|
t1.taskId, -1,
|
||||||
|
t2.taskId, t4.taskId,
|
||||||
|
t3.taskId, t5.taskId,
|
||||||
|
t6.taskId, -1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetRecentTasks_groupActiveFreeformTasks() {
|
public void testGetRecentTasks_groupActiveFreeformTasks() {
|
||||||
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
|
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
|
||||||
@@ -296,7 +331,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
|
|||||||
for (ActivityManager.RecentTaskInfo task : tasks) {
|
for (ActivityManager.RecentTaskInfo task : tasks) {
|
||||||
rawList.add(task);
|
rawList.add(task);
|
||||||
}
|
}
|
||||||
doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(),
|
doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
|
||||||
anyInt());
|
anyInt());
|
||||||
return rawList;
|
return rawList;
|
||||||
}
|
}
|
||||||
@@ -307,7 +342,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
|
|||||||
* @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
|
* @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
|
||||||
* the grouped task list
|
* the grouped task list
|
||||||
*/
|
*/
|
||||||
private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks,
|
private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
|
||||||
int... expectedTaskIds) {
|
int... expectedTaskIds) {
|
||||||
int[] flattenedTaskIds = new int[recentTasks.size() * 2];
|
int[] flattenedTaskIds = new int[recentTasks.size() * 2];
|
||||||
for (int i = 0; i < recentTasks.size(); i++) {
|
for (int i = 0; i < recentTasks.size(); i++) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class MediaProjectionAppSelectorController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun List<RecentTask>.sortTasks(): List<RecentTask> =
|
private fun List<RecentTask>.sortTasks(): List<RecentTask> =
|
||||||
asReversed().sortedBy {
|
sortedBy {
|
||||||
// Show normal tasks first and only then tasks with opened app selector
|
// Show normal tasks first and only then tasks with opened app selector
|
||||||
it.topActivityComponent == appSelectorComponentName
|
it.topActivityComponent == appSelectorComponentName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,17 @@
|
|||||||
package com.android.systemui.mediaprojection.appselector.data
|
package com.android.systemui.mediaprojection.appselector.data
|
||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.ComponentInfoFlags
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.UserHandle
|
||||||
|
import com.android.launcher3.icons.BaseIconFactory.IconOptions
|
||||||
|
import com.android.launcher3.icons.IconFactory
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
interface AppIconLoader {
|
interface AppIconLoader {
|
||||||
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
|
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
|
||||||
@@ -31,11 +37,20 @@ class IconLoaderLibAppIconLoader
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||||
|
private val context: Context,
|
||||||
|
private val packageManager: PackageManager
|
||||||
) : AppIconLoader {
|
) : AppIconLoader {
|
||||||
|
|
||||||
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
|
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
|
||||||
withContext(backgroundDispatcher) {
|
withContext(backgroundDispatcher) {
|
||||||
// TODO(b/240924731): add a blocking call to load an icon using iconloaderlib
|
IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
|
||||||
null
|
val activityInfo = packageManager
|
||||||
|
.getActivityInfo(component, ComponentInfoFlags.of(0))
|
||||||
|
val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
|
||||||
|
val userHandler = UserHandle.of(userId)
|
||||||
|
val options = IconOptions().apply { setUser(userHandler) }
|
||||||
|
val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
|
||||||
|
badgedIcon.newIcon(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,13 @@
|
|||||||
|
|
||||||
package com.android.systemui.mediaprojection.appselector.data
|
package com.android.systemui.mediaprojection.appselector.data
|
||||||
|
|
||||||
|
import android.annotation.ColorInt
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
|
||||||
data class RecentTask(
|
data class RecentTask(
|
||||||
val taskId: Int,
|
val taskId: Int,
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
val topActivityComponent: ComponentName?,
|
val topActivityComponent: ComponentName?,
|
||||||
val baseIntentComponent: ComponentName?
|
val baseIntentComponent: ComponentName?,
|
||||||
|
@ColorInt val colorBackground: Int?
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,23 +16,61 @@
|
|||||||
|
|
||||||
package com.android.systemui.mediaprojection.appselector.data
|
package com.android.systemui.mediaprojection.appselector.data
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
|
import com.android.systemui.util.kotlin.getOrNull
|
||||||
|
import com.android.wm.shell.recents.RecentTasks
|
||||||
|
import com.android.wm.shell.util.GroupedRecentTaskInfo
|
||||||
|
import java.util.Optional
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
interface RecentTaskListProvider {
|
interface RecentTaskListProvider {
|
||||||
|
/** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
|
||||||
suspend fun loadRecentTasks(): List<RecentTask>
|
suspend fun loadRecentTasks(): List<RecentTask>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ShellRecentTaskListProvider
|
class ShellRecentTaskListProvider
|
||||||
@Inject
|
@Inject
|
||||||
constructor(@Background private val coroutineDispatcher: CoroutineDispatcher) :
|
constructor(
|
||||||
RecentTaskListProvider {
|
@Background private val coroutineDispatcher: CoroutineDispatcher,
|
||||||
|
@Background private val backgroundExecutor: Executor,
|
||||||
|
private val recentTasks: Optional<RecentTasks>
|
||||||
|
) : RecentTaskListProvider {
|
||||||
|
|
||||||
|
private val recents by lazy { recentTasks.getOrNull() }
|
||||||
|
|
||||||
override suspend fun loadRecentTasks(): List<RecentTask> =
|
override suspend fun loadRecentTasks(): List<RecentTask> =
|
||||||
withContext(coroutineDispatcher) {
|
withContext(coroutineDispatcher) {
|
||||||
// TODO(b/240924731): add blocking call to load the recents
|
val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
|
||||||
emptyList()
|
|
||||||
|
rawRecentTasks
|
||||||
|
.flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
|
||||||
|
.map {
|
||||||
|
RecentTask(
|
||||||
|
it.taskId,
|
||||||
|
it.userId,
|
||||||
|
it.topActivity,
|
||||||
|
it.baseIntent?.component,
|
||||||
|
it.taskDescription?.backgroundColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
|
||||||
|
suspendCoroutine { continuation ->
|
||||||
|
getRecentTasks(
|
||||||
|
Integer.MAX_VALUE,
|
||||||
|
RECENT_IGNORE_UNAVAILABLE,
|
||||||
|
ActivityManager.getCurrentUser(),
|
||||||
|
backgroundExecutor
|
||||||
|
) { tasks ->
|
||||||
|
continuation.resume(tasks)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data
|
|||||||
|
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.shared.recents.model.ThumbnailData
|
import com.android.systemui.shared.recents.model.ThumbnailData
|
||||||
|
import com.android.systemui.shared.system.ActivityManagerWrapper
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -30,12 +31,13 @@ class ActivityTaskManagerThumbnailLoader
|
|||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Background private val coroutineDispatcher: CoroutineDispatcher,
|
@Background private val coroutineDispatcher: CoroutineDispatcher,
|
||||||
) :
|
private val activityManager: ActivityManagerWrapper
|
||||||
RecentTaskThumbnailLoader {
|
) : RecentTaskThumbnailLoader {
|
||||||
|
|
||||||
override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
|
override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
|
||||||
withContext(coroutineDispatcher) {
|
withContext(coroutineDispatcher) {
|
||||||
// TODO(b/240924731): add blocking call to load a thumbnail
|
val thumbnailData =
|
||||||
null
|
activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
|
||||||
|
if (thumbnailData.thumbnail == null) null else thumbnailData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInReverse() {
|
fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
|
||||||
val tasks = listOf(
|
val tasks = listOf(
|
||||||
createRecentTask(taskId = 1),
|
createRecentTask(taskId = 1),
|
||||||
createRecentTask(taskId = 2),
|
createRecentTask(taskId = 2),
|
||||||
@@ -66,15 +66,15 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
verify(view).bind(
|
verify(view).bind(
|
||||||
listOf(
|
listOf(
|
||||||
createRecentTask(taskId = 3),
|
|
||||||
createRecentTask(taskId = 2),
|
|
||||||
createRecentTask(taskId = 1),
|
createRecentTask(taskId = 1),
|
||||||
|
createRecentTask(taskId = 2),
|
||||||
|
createRecentTask(taskId = 3),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initRecentTasksWithAppSelectorTasks_bindsListInReverseAndAppSelectorTasksAtTheEnd() {
|
fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
|
||||||
val tasks = listOf(
|
val tasks = listOf(
|
||||||
createRecentTask(taskId = 1),
|
createRecentTask(taskId = 1),
|
||||||
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
|
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
|
||||||
@@ -88,11 +88,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
|||||||
|
|
||||||
verify(view).bind(
|
verify(view).bind(
|
||||||
listOf(
|
listOf(
|
||||||
createRecentTask(taskId = 5),
|
|
||||||
createRecentTask(taskId = 3),
|
|
||||||
createRecentTask(taskId = 1),
|
createRecentTask(taskId = 1),
|
||||||
createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
|
createRecentTask(taskId = 3),
|
||||||
|
createRecentTask(taskId = 5),
|
||||||
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
|
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
|
||||||
|
createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
|
|||||||
taskId = taskId,
|
taskId = taskId,
|
||||||
topActivityComponent = topActivityComponent,
|
topActivityComponent = topActivityComponent,
|
||||||
baseIntentComponent = ComponentName("com", "Test"),
|
baseIntentComponent = ComponentName("com", "Test"),
|
||||||
userId = 0
|
userId = 0,
|
||||||
|
colorBackground = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.android.systemui.mediaprojection.appselector.data
|
||||||
|
|
||||||
|
import android.app.ActivityManager.RecentTaskInfo
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
|
import com.android.wm.shell.recents.RecentTasks
|
||||||
|
import com.android.wm.shell.util.GroupedRecentTaskInfo
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.util.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@SmallTest
|
||||||
|
class ShellRecentTaskListProviderTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val dispatcher = Dispatchers.Unconfined
|
||||||
|
private val recentTasks: RecentTasks = mock()
|
||||||
|
private val recentTaskListProvider =
|
||||||
|
ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadRecentTasks_oneTask_returnsTheSameTask() {
|
||||||
|
givenRecentTasks(createSingleTask(taskId = 1))
|
||||||
|
|
||||||
|
val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
|
||||||
|
|
||||||
|
assertThat(result).containsExactly(createRecentTask(taskId = 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadRecentTasks_multipleTasks_returnsTheSameTasks() {
|
||||||
|
givenRecentTasks(
|
||||||
|
createSingleTask(taskId = 1),
|
||||||
|
createSingleTask(taskId = 2),
|
||||||
|
createSingleTask(taskId = 3),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.containsExactly(
|
||||||
|
createRecentTask(taskId = 1),
|
||||||
|
createRecentTask(taskId = 2),
|
||||||
|
createRecentTask(taskId = 3),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadRecentTasks_groupedTask_returnsUngroupedTasks() {
|
||||||
|
givenRecentTasks(createTaskPair(taskId1 = 1, taskId2 = 2))
|
||||||
|
|
||||||
|
val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadRecentTasks_mixedSingleAndGroupedTask_returnsUngroupedTasks() {
|
||||||
|
givenRecentTasks(
|
||||||
|
createSingleTask(taskId = 1),
|
||||||
|
createTaskPair(taskId1 = 2, taskId2 = 3),
|
||||||
|
createSingleTask(taskId = 4),
|
||||||
|
createTaskPair(taskId1 = 5, taskId2 = 6),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
|
||||||
|
|
||||||
|
assertThat(result)
|
||||||
|
.containsExactly(
|
||||||
|
createRecentTask(taskId = 1),
|
||||||
|
createRecentTask(taskId = 2),
|
||||||
|
createRecentTask(taskId = 3),
|
||||||
|
createRecentTask(taskId = 4),
|
||||||
|
createRecentTask(taskId = 5),
|
||||||
|
createRecentTask(taskId = 6),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
|
||||||
|
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
|
||||||
|
val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
|
||||||
|
consumer.accept(tasks.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createRecentTask(taskId: Int): RecentTask =
|
||||||
|
RecentTask(
|
||||||
|
taskId = taskId,
|
||||||
|
userId = 0,
|
||||||
|
topActivityComponent = null,
|
||||||
|
baseIntentComponent = null,
|
||||||
|
colorBackground = null
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
|
||||||
|
GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
|
||||||
|
|
||||||
|
private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
|
||||||
|
GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
|
||||||
|
|
||||||
|
private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user