1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.carlauncher.recents; 18 19 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; 20 21 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM; 22 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SINGLE; 23 import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_SPLIT; 24 25 import static com.google.common.truth.Truth.assertThat; 26 27 import static org.mockito.ArgumentMatchers.any; 28 import static org.mockito.ArgumentMatchers.anyBoolean; 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.ArgumentMatchers.eq; 31 import static org.mockito.ArgumentMatchers.nullable; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.never; 34 import static org.mockito.Mockito.verify; 35 import static org.mockito.Mockito.when; 36 37 import android.app.Activity; 38 import android.app.ActivityManager; 39 import android.app.ActivityOptions; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.pm.ActivityInfo; 44 import android.content.pm.ApplicationInfo; 45 import android.content.pm.PackageManager; 46 import android.graphics.Bitmap; 47 import android.graphics.drawable.BitmapDrawable; 48 import android.graphics.drawable.Drawable; 49 import android.os.Handler; 50 import android.os.RemoteException; 51 import android.testing.TestableContext; 52 53 import androidx.test.platform.app.InstrumentationRegistry; 54 55 import com.android.systemui.shared.recents.model.Task; 56 import com.android.systemui.shared.system.ActivityManagerWrapper; 57 import com.android.systemui.shared.system.PackageManagerWrapper; 58 import com.android.wm.shell.recents.IRecentTasks; 59 import com.android.wm.shell.util.GroupedRecentTaskInfo; 60 61 import com.google.common.util.concurrent.MoreExecutors; 62 63 import org.junit.After; 64 import org.junit.Before; 65 import org.junit.Rule; 66 import org.junit.Test; 67 import org.mockito.Mock; 68 import org.mockito.MockitoAnnotations; 69 import org.mockito.stubbing.Answer; 70 71 import java.util.ArrayList; 72 import java.util.List; 73 74 75 public class RecentTasksProviderTest { 76 private static final int RECENT_TASKS_LENGTH = 20; 77 private static final int SPLIT_RECENT_TASKS_LENGTH = 2; 78 private static final int FREEFORM_RECENT_TASKS_LENGTH = 3; 79 80 private RecentTasksProvider mRecentTasksProvider; 81 private GroupedRecentTaskInfo[] mGroupedRecentTaskInfo; 82 83 @Mock 84 private IRecentTasks mRecentTaskProxy; 85 @Mock 86 private ActivityManagerWrapper mActivityManagerWrapper; 87 @Mock 88 private PackageManagerWrapper mPackageManagerWrapper; 89 @Mock 90 private RecentTasksProviderInterface.RecentsDataChangeListener mRecentsDataChangeListenerMock; 91 @Mock 92 private Task mTask; 93 @Mock 94 private Task.TaskKey mTaskKey; 95 @Mock 96 private ActivityManager.TaskDescription mTaskDescription; 97 @Mock 98 private Bitmap mIconBitmap; 99 @Mock 100 private Drawable mIconDrawable; 101 @Mock 102 private Drawable mDefaultIconDrawable; 103 @Mock 104 private ComponentName mComponent; 105 @Mock 106 private ActivityInfo mActivityInfo; 107 @Mock 108 private Intent mBaseIntent; 109 @Mock 110 private Handler mHandler; 111 112 @Rule 113 public final TestableContext mContext = new TestableContext( 114 InstrumentationRegistry.getInstrumentation().getTargetContext()) { 115 @Override 116 public Context createApplicationContext(ApplicationInfo application, int flags) { 117 return this; 118 } 119 }; 120 121 @Before setup()122 public void setup() throws RemoteException { 123 MockitoAnnotations.initMocks(this); 124 initRecentTaskList(); 125 when(mRecentTaskProxy.getRecentTasks(anyInt(), eq(RECENT_IGNORE_UNAVAILABLE), 126 anyInt())).thenReturn(mGroupedRecentTaskInfo); 127 RecentTasksProvider.setExecutor(MoreExecutors.directExecutor()); 128 when(mHandler.post(any(Runnable.class))).thenAnswer((Answer<Runnable>) invocation -> { 129 ((Runnable) invocation.getArgument(0)).run(); 130 return null; 131 }); 132 RecentTasksProvider.setHandler(mHandler); 133 mRecentTasksProvider = RecentTasksProvider.getInstance(); 134 mRecentTasksProvider.setActivityManagerWrapper(mActivityManagerWrapper); 135 mRecentTasksProvider.setPackageManagerWrapper(mPackageManagerWrapper); 136 mRecentTasksProvider.init(mContext, mRecentTaskProxy); 137 } 138 139 @After cleanup()140 public void cleanup() { 141 mRecentTasksProvider.mRecentTaskIdToTaskMap.clear(); 142 } 143 144 @Test getRecentTasksAsync_triggers_recentTasksFetched()145 public void getRecentTasksAsync_triggers_recentTasksFetched() throws InterruptedException { 146 mRecentTasksProvider.setRecentsDataChangeListener(mRecentsDataChangeListenerMock); 147 148 mRecentTasksProvider.getRecentTasksAsync(); 149 150 verify(mRecentsDataChangeListenerMock).recentTasksFetched(); 151 } 152 153 @Test getRecentTasksAsync_trigger_recentTaskThumbnailChange_forAllTasks()154 public void getRecentTasksAsync_trigger_recentTaskThumbnailChange_forAllTasks() { 155 mRecentTasksProvider.setRecentsDataChangeListener(mRecentsDataChangeListenerMock); 156 157 mRecentTasksProvider.getRecentTasksAsync(); 158 159 for (int i = 0; i < RECENT_TASKS_LENGTH; i++) { 160 verify(mRecentsDataChangeListenerMock).recentTaskThumbnailChange(i); 161 } 162 } 163 164 @Test getRecentTasksAsync_trigger_recentTaskIconChange_forAllTasks()165 public void getRecentTasksAsync_trigger_recentTaskIconChange_forAllTasks() { 166 mRecentTasksProvider.setRecentsDataChangeListener(mRecentsDataChangeListenerMock); 167 168 mRecentTasksProvider.getRecentTasksAsync(); 169 170 for (int i = 0; i < RECENT_TASKS_LENGTH; i++) { 171 verify(mRecentsDataChangeListenerMock).recentTaskIconChange(i); 172 } 173 } 174 175 @Test getRecentTasksAsync_getRecentTaskIds_returnsAllInOrder_fetchedTaskIds()176 public void getRecentTasksAsync_getRecentTaskIds_returnsAllInOrder_fetchedTaskIds() { 177 mRecentTasksProvider.setRecentsDataChangeListener( 178 new ConvenienceRecentsDataChangeListener() { 179 @Override 180 public void recentTasksFetched() { 181 List<Integer> ret = mRecentTasksProvider.getRecentTaskIds(); 182 183 assertThat(ret).isNotNull(); 184 assertThat(ret.size()).isEqualTo(RECENT_TASKS_LENGTH); 185 for (int i = 0; i < RECENT_TASKS_LENGTH; i++) { 186 assertThat(ret.get(i)).isEqualTo(i); 187 } 188 } 189 }); 190 191 mRecentTasksProvider.getRecentTasksAsync(); 192 } 193 194 @Test getRecentTasksAsync_getRecentTaskIds_filters_TYPE_SPLIT()195 public void getRecentTasksAsync_getRecentTaskIds_filters_TYPE_SPLIT() throws 196 RemoteException { 197 initRecentTaskList(/* addTypeSplit= */ true, /* addTypeFreeform= */ false); 198 assertThat(mGroupedRecentTaskInfo.length).isEqualTo( 199 RECENT_TASKS_LENGTH + SPLIT_RECENT_TASKS_LENGTH); 200 when(mRecentTaskProxy.getRecentTasks(anyInt(), eq(RECENT_IGNORE_UNAVAILABLE), 201 anyInt())).thenReturn(mGroupedRecentTaskInfo); 202 203 mRecentTasksProvider.setRecentsDataChangeListener( 204 new ConvenienceRecentsDataChangeListener() { 205 @Override 206 public void recentTasksFetched() { 207 List<Integer> ret = mRecentTasksProvider.getRecentTaskIds(); 208 209 assertThat(ret).isNotNull(); 210 assertThat(ret.size()).isEqualTo(RECENT_TASKS_LENGTH); 211 } 212 }); 213 214 mRecentTasksProvider.getRecentTasksAsync(); 215 } 216 217 @Test getRecentTasksAsync_getRecentTaskIds_filters_TYPE_FREEFORM()218 public void getRecentTasksAsync_getRecentTaskIds_filters_TYPE_FREEFORM() throws 219 RemoteException { 220 initRecentTaskList(/* addTypeSplit= */ false, /* addTypeFreeform= */ true); 221 assertThat(mGroupedRecentTaskInfo.length).isEqualTo( 222 RECENT_TASKS_LENGTH + FREEFORM_RECENT_TASKS_LENGTH); 223 when(mRecentTaskProxy.getRecentTasks(anyInt(), eq(RECENT_IGNORE_UNAVAILABLE), 224 anyInt())).thenReturn(mGroupedRecentTaskInfo); 225 226 mRecentTasksProvider.setRecentsDataChangeListener( 227 new ConvenienceRecentsDataChangeListener() { 228 @Override 229 public void recentTasksFetched() { 230 List<Integer> ret = mRecentTasksProvider.getRecentTaskIds(); 231 232 assertThat(ret).isNotNull(); 233 assertThat(ret.size()).isEqualTo(RECENT_TASKS_LENGTH); 234 } 235 }); 236 237 mRecentTasksProvider.getRecentTasksAsync(); 238 } 239 240 @Test getRecentTaskIconAsync_sets_iconFromTaskDescription()241 public void getRecentTaskIconAsync_sets_iconFromTaskDescription() { 242 int taskId = 500; 243 when(mTaskDescription.getInMemoryIcon()).thenReturn(mIconBitmap); 244 mTask.taskDescription = mTaskDescription; 245 when(mTaskKey.getComponent()).thenReturn(mComponent); 246 mTask.key = mTaskKey; 247 mRecentTasksProvider.mRecentTaskIdToTaskMap.put(taskId, mTask); 248 249 mRecentTasksProvider.setRecentsDataChangeListener( 250 new ConvenienceRecentsDataChangeListener() { 251 @Override 252 public void recentTaskIconChange(int taskId) { 253 Drawable d = mRecentTasksProvider.getRecentTaskIcon(taskId); 254 255 assertThat(d instanceof BitmapDrawable).isTrue(); 256 assertThat(((BitmapDrawable) d).getBitmap()).isEqualTo(mIconBitmap); 257 } 258 }); 259 260 mRecentTasksProvider.getRecentTaskIconAsync(taskId); 261 } 262 263 @Test getRecentTaskIconAsync_sets_iconFromPackageManager()264 public void getRecentTaskIconAsync_sets_iconFromPackageManager() { 265 int taskId = 500; 266 when(mTaskDescription.getInMemoryIcon()).thenReturn(null); 267 mTask.taskDescription = mTaskDescription; 268 when(mTaskKey.getComponent()).thenReturn(mComponent); 269 mTask.key = mTaskKey; 270 when(mActivityInfo.loadIcon(any(PackageManager.class))).thenReturn(mIconDrawable); 271 when(mPackageManagerWrapper.getActivityInfo(eq(mComponent), anyInt())).thenReturn( 272 mActivityInfo); 273 mRecentTasksProvider.mRecentTaskIdToTaskMap.put(taskId, mTask); 274 275 mRecentTasksProvider.setRecentsDataChangeListener( 276 new ConvenienceRecentsDataChangeListener() { 277 @Override 278 public void recentTaskIconChange(int taskId) { 279 Drawable d = mRecentTasksProvider.getRecentTaskIcon(taskId); 280 281 assertThat(d).isEqualTo(mIconDrawable); 282 } 283 }); 284 285 mRecentTasksProvider.getRecentTaskIconAsync(taskId); 286 } 287 288 @Test getRecentTaskIconAsync_sets_defaultIcon()289 public void getRecentTaskIconAsync_sets_defaultIcon() { 290 int taskId = 500; 291 when(mTaskDescription.getInMemoryIcon()).thenReturn(null); 292 mTask.taskDescription = mTaskDescription; 293 when(mTaskKey.getComponent()).thenReturn(mComponent); 294 mTask.key = mTaskKey; 295 when(mPackageManagerWrapper.getActivityInfo(eq(mComponent), anyInt())).thenReturn(null); 296 mRecentTasksProvider.mRecentTaskIdToTaskMap.put(taskId, mTask); 297 mRecentTasksProvider.setDefaultIcon(mDefaultIconDrawable); 298 299 mRecentTasksProvider.setRecentsDataChangeListener( 300 new ConvenienceRecentsDataChangeListener() { 301 @Override 302 public void recentTaskIconChange(int taskId) { 303 Drawable d = mRecentTasksProvider.getRecentTaskIcon(taskId); 304 305 assertThat(d).isEqualTo(mDefaultIconDrawable); 306 } 307 }); 308 309 mRecentTasksProvider.getRecentTaskIconAsync(taskId); 310 } 311 312 @Test openTopRunningTask_openRunningTaskAfterRecentsActivity()313 public void openTopRunningTask_openRunningTaskAfterRecentsActivity() { 314 int displayId = 0; 315 int tasksBeforeRecents = 2; 316 int tasksAfterRecents = 2; 317 ActivityManager.RunningTaskInfo[] infos = createRunningTaskList( 318 tasksBeforeRecents, /* addRecentsClass= */ true, tasksAfterRecents, 319 /* recentsClazz= */ RECENTS_ACTIVITY.class.getName()); 320 when(mActivityManagerWrapper.getRunningTasks(anyBoolean(), eq(displayId))) 321 .thenReturn(infos); 322 ActivityManager.RunningTaskInfo taskAfterRecents = infos[tasksBeforeRecents + 1]; 323 324 mRecentTasksProvider.openTopRunningTask(RECENTS_ACTIVITY.class, displayId); 325 326 verify(mActivityManagerWrapper).startActivityFromRecents(eq(taskAfterRecents.taskId), 327 nullable(ActivityOptions.class)); 328 } 329 330 @Test openTopRunningTask_recentsActivityNotFound_noOp()331 public void openTopRunningTask_recentsActivityNotFound_noOp() { 332 int displayId = 0; 333 int tasksBeforeRecents = 2; 334 ActivityManager.RunningTaskInfo[] infos = createRunningTaskList( 335 tasksBeforeRecents, /* addRecentsClass= */ false, /* tasksAfterRecents= */ 0, 336 /* recentsClazz= */ RECENTS_ACTIVITY.class.getName()); 337 when(mActivityManagerWrapper.getRunningTasks(anyBoolean(), eq(displayId))) 338 .thenReturn(infos); 339 340 mRecentTasksProvider.openTopRunningTask(RECENTS_ACTIVITY.class, displayId); 341 342 verify(mActivityManagerWrapper, never()).startActivityFromRecents(anyInt(), 343 nullable(ActivityOptions.class)); 344 } 345 346 @Test openTopRunningTask_recentsActivityNotFound_returnsFalse()347 public void openTopRunningTask_recentsActivityNotFound_returnsFalse() { 348 int displayId = 0; 349 int tasksBeforeRecents = 2; 350 ActivityManager.RunningTaskInfo[] infos = createRunningTaskList( 351 tasksBeforeRecents, /* addRecentsClass= */ false, /* tasksAfterRecents= */ 0, 352 /* recentsClazz= */ RECENTS_ACTIVITY.class.getName()); 353 when(mActivityManagerWrapper.getRunningTasks(anyBoolean(), eq(displayId))) 354 .thenReturn(infos); 355 356 boolean ret = mRecentTasksProvider.openTopRunningTask(RECENTS_ACTIVITY.class, displayId); 357 358 assertThat(ret).isFalse(); 359 } 360 initRecentTaskList()361 private void initRecentTaskList() { 362 initRecentTaskList(/* addTypeSplit= */ false, /* addTypeFreeform= */ false); 363 } 364 initRecentTaskList(boolean addTypeSplit, boolean addTypeFreeform)365 private void initRecentTaskList(boolean addTypeSplit, boolean addTypeFreeform) { 366 List<GroupedRecentTaskInfo> groupedRecentTaskInfos = new ArrayList<>(); 367 for (int i = 0; i < RECENT_TASKS_LENGTH; i++) { 368 groupedRecentTaskInfos.add( 369 createGroupedRecentTaskInfo(createRecentTaskInfo(i), TYPE_SINGLE)); 370 } 371 if (addTypeSplit) { 372 for (int i = 0; i < SPLIT_RECENT_TASKS_LENGTH; i++) { 373 groupedRecentTaskInfos.add( 374 createGroupedRecentTaskInfo(createRecentTaskInfo(i), TYPE_SPLIT)); 375 } 376 } 377 if (addTypeFreeform) { 378 for (int i = 0; i < FREEFORM_RECENT_TASKS_LENGTH; i++) { 379 groupedRecentTaskInfos.add( 380 createGroupedRecentTaskInfo(createRecentTaskInfo(i), TYPE_FREEFORM)); 381 } 382 } 383 mGroupedRecentTaskInfo = groupedRecentTaskInfos.toArray(GroupedRecentTaskInfo[]::new); 384 } 385 createGroupedRecentTaskInfo(ActivityManager.RecentTaskInfo info, int type)386 private GroupedRecentTaskInfo createGroupedRecentTaskInfo(ActivityManager.RecentTaskInfo info, 387 int type) { 388 GroupedRecentTaskInfo groupedRecentTaskInfo = mock(GroupedRecentTaskInfo.class); 389 when(groupedRecentTaskInfo.getType()).thenReturn(type); 390 when(groupedRecentTaskInfo.getTaskInfo1()).thenReturn(info); 391 return groupedRecentTaskInfo; 392 } 393 createRecentTaskInfo(int taskId)394 private ActivityManager.RecentTaskInfo createRecentTaskInfo(int taskId) { 395 when(mBaseIntent.getComponent()).thenReturn(mComponent); 396 ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); 397 recentTaskInfo.taskId = taskId; 398 recentTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class); 399 recentTaskInfo.baseIntent = mBaseIntent; 400 return recentTaskInfo; 401 } 402 createRunningTaskList(int tasksBeforeRecents, boolean addRecentsClass, int tasksAfterRecents, String recentsClazz)403 private ActivityManager.RunningTaskInfo[] createRunningTaskList(int tasksBeforeRecents, 404 boolean addRecentsClass, int tasksAfterRecents, String recentsClazz) { 405 int length = 406 addRecentsClass ? tasksBeforeRecents + 1 + tasksAfterRecents : tasksBeforeRecents; 407 ActivityManager.RunningTaskInfo[] infos = new ActivityManager.RunningTaskInfo[length]; 408 for (int i = 0; i < length; i++) { 409 ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); 410 info.taskId = i; 411 if (i == tasksBeforeRecents) { 412 info.topActivity = new ComponentName("pkg-" + i, recentsClazz); 413 } else { 414 info.topActivity = new ComponentName("pkg-" + i, "class-" + i); 415 } 416 infos[i] = info; 417 } 418 return infos; 419 } 420 421 private abstract static class ConvenienceRecentsDataChangeListener implements 422 RecentTasksProviderInterface.RecentsDataChangeListener { 423 @Override recentTasksFetched()424 public void recentTasksFetched() { 425 // no-op 426 } 427 428 @Override recentTaskThumbnailChange(int taskId)429 public void recentTaskThumbnailChange(int taskId) { 430 // no-op 431 } 432 433 @Override recentTaskIconChange(int taskId)434 public void recentTaskIconChange(int taskId) { 435 // no-op 436 } 437 } 438 439 private static class RECENTS_ACTIVITY extends Activity { 440 } 441 } 442