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 android.app.Activity; 26 import android.app.ActivityManager; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.ActivityInfo; 31 import android.content.res.Resources; 32 import android.graphics.Bitmap; 33 import android.graphics.Rect; 34 import android.graphics.drawable.BitmapDrawable; 35 import android.graphics.drawable.Drawable; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.Looper; 39 import android.os.RemoteException; 40 import android.util.Log; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 45 import com.android.systemui.shared.recents.model.Task; 46 import com.android.systemui.shared.recents.model.ThumbnailData; 47 import com.android.systemui.shared.system.ActivityManagerWrapper; 48 import com.android.systemui.shared.system.PackageManagerWrapper; 49 import com.android.systemui.shared.system.TaskStackChangeListener; 50 import com.android.systemui.shared.system.TaskStackChangeListeners; 51 import com.android.wm.shell.recents.IRecentTasks; 52 import com.android.wm.shell.util.GroupedRecentTaskInfo; 53 54 import com.google.common.annotations.VisibleForTesting; 55 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.concurrent.Executor; 62 import java.util.concurrent.Executors; 63 64 public class RecentTasksProvider implements RecentTasksProviderInterface { 65 private static final String TAG = "RecentTasksProviderInterface"; 66 private static final boolean DEBUG = Build.IS_DEBUGGABLE; 67 private static Executor sRecentsModelExecutor = Executors.newSingleThreadExecutor(); 68 private static Handler sMainHandler = new Handler(Looper.getMainLooper()); 69 private static RecentTasksProvider sInstance; 70 private Context mContext; 71 private IRecentTasks mRecentTasksProxy; 72 private ActivityManagerWrapper mActivityManagerWrapper; 73 private PackageManagerWrapper mPackageManagerWrapper; 74 private Drawable mDefaultIcon; 75 private List<Integer> mRecentTaskIds; 76 @VisibleForTesting 77 Map<Integer, Task> mRecentTaskIdToTaskMap; 78 private RecentsDataChangeListener mRecentsDataChangeListener; 79 private boolean mIsInitialised; 80 private final TaskStackChangeListener mTaskStackChangeListener = new TaskStackChangeListener() { 81 @Override 82 public boolean onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) { 83 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 84 return false; 85 } 86 mRecentTaskIdToTaskMap.get(taskId).thumbnail = snapshot; 87 if (mRecentsDataChangeListener != null) { 88 sMainHandler.post( 89 () -> mRecentsDataChangeListener.recentTaskThumbnailChange(taskId)); 90 } 91 return true; 92 } 93 94 @Override 95 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) { 96 if (!mRecentTaskIdToTaskMap.containsKey(taskInfo.taskId)) { 97 return; 98 } 99 Drawable icon = getIconFromTaskDescription(taskInfo.taskDescription); 100 if (icon == null) { 101 return; 102 } 103 mRecentTaskIdToTaskMap.get(taskInfo.taskId).icon = icon; 104 if (mRecentsDataChangeListener != null) { 105 sMainHandler.post( 106 () -> mRecentsDataChangeListener.recentTaskIconChange(taskInfo.taskId)); 107 } 108 } 109 }; 110 RecentTasksProvider()111 private RecentTasksProvider() { 112 mActivityManagerWrapper = ActivityManagerWrapper.getInstance(); 113 mPackageManagerWrapper = PackageManagerWrapper.getInstance(); 114 mRecentTaskIds = new ArrayList<>(); 115 mRecentTaskIdToTaskMap = new HashMap<>(); 116 mDefaultIcon = Objects.requireNonNull(Resources.getSystem().getDrawable( 117 android.R.drawable.sym_def_app_icon, /* theme= */ null)); 118 } 119 getInstance()120 public static RecentTasksProvider getInstance() { 121 if (sInstance == null) { 122 sInstance = new RecentTasksProvider(); 123 } 124 return sInstance; 125 } 126 init(Context context, IRecentTasks recentTasksProxy)127 public void init(Context context, IRecentTasks recentTasksProxy) { 128 if (mIsInitialised) { 129 return; 130 } 131 mIsInitialised = true; 132 mContext = context; 133 mRecentTasksProxy = recentTasksProxy; 134 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackChangeListener); 135 } 136 137 /** 138 * Terminates connections and sets shared service variables to {@code null}. 139 */ terminate()140 public void terminate() { 141 mIsInitialised = false; 142 mContext = null; 143 mRecentTasksProxy = null; 144 TaskStackChangeListeners.getInstance().unregisterTaskStackListener( 145 mTaskStackChangeListener); 146 clearCache(); 147 } 148 149 @Override getRecentTasksAsync()150 public void getRecentTasksAsync() { 151 if (mRecentTasksProxy == null) { 152 return; 153 } 154 sRecentsModelExecutor.execute(() -> { 155 GroupedRecentTaskInfo[] groupedRecentTasks; 156 try { 157 // todo: b/271498799 use ActivityManagerWrapper.getInstance().getCurrentUserId() 158 // or equivalent instead of hidden API mContext.getUserId() 159 groupedRecentTasks = 160 mRecentTasksProxy.getRecentTasks(Integer.MAX_VALUE, 161 RECENT_IGNORE_UNAVAILABLE, mContext.getUserId()); 162 } catch (RemoteException e) { 163 if (DEBUG) { 164 Log.e(TAG, e.toString()); 165 } 166 return; 167 } 168 if (groupedRecentTasks == null) { 169 return; 170 } 171 mRecentTaskIds = new ArrayList<>(groupedRecentTasks.length); 172 mRecentTaskIdToTaskMap = new HashMap<>(groupedRecentTasks.length); 173 boolean areSplitOrFreeformTypeTasksPresent = false; 174 for (GroupedRecentTaskInfo groupedRecentTask : groupedRecentTasks) { 175 switch (groupedRecentTask.getType()) { 176 case TYPE_SINGLE: 177 // Automotive doesn't have split screen functionality, only process tasks 178 // of TYPE_SINGLE. 179 ActivityManager.RecentTaskInfo taskInfo = groupedRecentTask.getTaskInfo1(); 180 Task.TaskKey taskKey = new Task.TaskKey(taskInfo); 181 182 // isLocked is always set to false since this value is not required in 183 // automotive. Usually set as Keyguard lock state for the user associated 184 // with the task. 185 // ag/1705623 introduced this to support multiple profiles under same user 186 // where this value is necessary to check if profile user associated with 187 // the task is unlocked. 188 Task task = Task.from(taskKey, taskInfo, /* isLocked= */ false); 189 task.setLastSnapshotData(taskInfo); 190 mRecentTaskIds.add(task.key.id); 191 mRecentTaskIdToTaskMap.put(task.key.id, task); 192 getRecentTaskThumbnailAsync(task.key.id); 193 getRecentTaskIconAsync(task.key.id); 194 break; 195 case TYPE_SPLIT: 196 case TYPE_FREEFORM: 197 areSplitOrFreeformTypeTasksPresent = true; 198 } 199 } 200 if (areSplitOrFreeformTypeTasksPresent && DEBUG) { 201 Log.d(TAG, "Automotive doesn't support TYPE_SPLIT and TYPE_FREEFORM tasks"); 202 } 203 if (mRecentsDataChangeListener != null) { 204 sMainHandler.post(() -> mRecentsDataChangeListener.recentTasksFetched()); 205 } 206 }); 207 } 208 209 @NonNull 210 @Override getRecentTaskIds()211 public List<Integer> getRecentTaskIds() { 212 return new ArrayList<>(mRecentTaskIds); 213 } 214 215 @Nullable 216 @Override getRecentTaskComponentName(int taskId)217 public ComponentName getRecentTaskComponentName(int taskId) { 218 return mRecentTaskIdToTaskMap.containsKey(taskId) 219 ? mRecentTaskIdToTaskMap.get(taskId).getTopComponent() : null; 220 } 221 222 @Nullable 223 @Override getRecentTaskBaseIntent(int taskId)224 public Intent getRecentTaskBaseIntent(int taskId) { 225 return mRecentTaskIdToTaskMap.containsKey(taskId) 226 ? mRecentTaskIdToTaskMap.get(taskId).getKey().baseIntent : null; 227 } 228 229 @Nullable 230 @Override getRecentTaskIcon(int taskId)231 public Drawable getRecentTaskIcon(int taskId) { 232 return mRecentTaskIdToTaskMap.containsKey(taskId) 233 ? mRecentTaskIdToTaskMap.get(taskId).icon : null; 234 } 235 236 @Nullable 237 @Override getRecentTaskThumbnail(int taskId)238 public Bitmap getRecentTaskThumbnail(int taskId) { 239 ThumbnailData thumbnailData = getRecentTaskThumbnailData(taskId); 240 return thumbnailData != null ? thumbnailData.getThumbnail() : null; 241 } 242 243 @NonNull 244 @Override getRecentTaskInsets(int taskId)245 public Rect getRecentTaskInsets(int taskId) { 246 ThumbnailData thumbnailData = getRecentTaskThumbnailData(taskId); 247 return thumbnailData != null ? thumbnailData.insets : new Rect(); 248 249 } 250 251 @Nullable getRecentTaskThumbnailData(int taskId)252 private ThumbnailData getRecentTaskThumbnailData(int taskId) { 253 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 254 return null; 255 } 256 return mRecentTaskIdToTaskMap.get(taskId).thumbnail; 257 } 258 259 @Override openRecentTask(int taskId)260 public boolean openRecentTask(int taskId) { 261 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 262 return false; 263 } 264 return ActivityManagerWrapper.getInstance().startActivityFromRecents( 265 mRecentTaskIdToTaskMap.get(taskId).key, /* options= */ null); 266 } 267 268 @Override openTopRunningTask(@onNull Class<? extends Activity> recentsActivity, int displayId)269 public boolean openTopRunningTask(@NonNull Class<? extends Activity> recentsActivity, 270 int displayId) { 271 ActivityManager.RunningTaskInfo[] runningTasks = mActivityManagerWrapper.getRunningTasks( 272 /* filterOnlyVisibleRecents= */ false, displayId); 273 boolean foundRecentsTask = false; 274 for (ActivityManager.RunningTaskInfo runningTask : runningTasks) { 275 if (runningTask == null) { 276 return false; 277 } 278 if (foundRecentsTask) { 279 // this is the running task after recents task, attempt to open 280 return mActivityManagerWrapper.startActivityFromRecents( 281 runningTask.taskId, /* options= */ null); 282 } 283 String topComponent = runningTask.topActivity != null 284 ? runningTask.topActivity.getClassName() 285 : runningTask.baseIntent.getComponent().getClassName(); 286 if (recentsActivity.getName().equals(topComponent)) { 287 foundRecentsTask = true; 288 } 289 } 290 // Recents task not found or no task present after recents task, 291 // not attempting to open any running task 292 return false; 293 } 294 295 @Override removeTaskFromRecents(int taskId)296 public void removeTaskFromRecents(int taskId) { 297 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 298 return; 299 } 300 mActivityManagerWrapper.removeTask(mRecentTaskIdToTaskMap.get(taskId).key.id); 301 mRecentTaskIds.remove((Integer) taskId); 302 } 303 304 @Override removeAllRecentTasks()305 public void removeAllRecentTasks() { 306 mActivityManagerWrapper.removeAllRecentTasks(); 307 clearCache(); 308 } 309 310 @Override clearCache()311 public void clearCache() { 312 mRecentTaskIds.clear(); 313 mRecentTaskIdToTaskMap.clear(); 314 } 315 316 @Override setRecentsDataChangeListener(@ullable RecentsDataChangeListener listener)317 public void setRecentsDataChangeListener(@Nullable RecentsDataChangeListener listener) { 318 mRecentsDataChangeListener = listener; 319 } 320 getRecentTaskThumbnailAsync(int taskId)321 private void getRecentTaskThumbnailAsync(int taskId) { 322 sRecentsModelExecutor.execute(() -> { 323 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 324 return; 325 } 326 ThumbnailData thumbnailData = mActivityManagerWrapper.getTaskThumbnail( 327 taskId, /* isLowResolution= */ false); 328 mRecentTaskIdToTaskMap.get(taskId).thumbnail = thumbnailData; 329 if (mRecentsDataChangeListener != null) { 330 sMainHandler.post( 331 () -> mRecentsDataChangeListener.recentTaskThumbnailChange(taskId)); 332 } 333 }); 334 } 335 336 @VisibleForTesting getRecentTaskIconAsync(int taskId)337 void getRecentTaskIconAsync(int taskId) { 338 sRecentsModelExecutor.execute(() -> { 339 if (!mRecentTaskIdToTaskMap.containsKey(taskId)) { 340 return; 341 } 342 Task task = mRecentTaskIdToTaskMap.get(taskId); 343 Task.TaskKey key = task.key; 344 Drawable drawableIcon = getIconFromTaskDescription(task.taskDescription); 345 346 if (drawableIcon == null) { 347 ActivityInfo activityInfo = mPackageManagerWrapper.getActivityInfo( 348 key.getComponent(), key.userId); 349 if (activityInfo != null) { 350 drawableIcon = activityInfo.loadIcon(mContext.getPackageManager()); 351 } else { 352 // set it a default icon 353 drawableIcon = mDefaultIcon; 354 } 355 } 356 mRecentTaskIdToTaskMap.get(taskId).icon = drawableIcon; 357 if (mRecentsDataChangeListener != null) { 358 sMainHandler.post( 359 () -> mRecentsDataChangeListener.recentTaskIconChange(taskId)); 360 } 361 }); 362 } 363 364 @Nullable getIconFromTaskDescription( ActivityManager.TaskDescription taskDescription)365 private Drawable getIconFromTaskDescription( 366 ActivityManager.TaskDescription taskDescription) { 367 Bitmap icon; 368 // todo: b/271498799 access through ActivityManagerWrapper instead of using 369 // hidden api getInMemoryIcon(), loadTaskDescriptionIcon() and mContext.getUserId() 370 if (taskDescription.getInMemoryIcon() != null) { 371 icon = taskDescription.getInMemoryIcon(); 372 } else { 373 icon = ActivityManager.TaskDescription.loadTaskDescriptionIcon( 374 taskDescription.getIconFilename(), mContext.getUserId()); 375 } 376 return icon != null ? new BitmapDrawable(mContext.getResources(), icon) : null; 377 } 378 379 @VisibleForTesting setExecutor(Executor executor)380 static void setExecutor(Executor executor) { 381 sRecentsModelExecutor = executor; 382 } 383 384 @VisibleForTesting setHandler(Handler handler)385 static void setHandler(Handler handler) { 386 sMainHandler = handler; 387 } 388 389 @VisibleForTesting setActivityManagerWrapper(ActivityManagerWrapper activityManagerWrapper)390 void setActivityManagerWrapper(ActivityManagerWrapper activityManagerWrapper) { 391 mActivityManagerWrapper = activityManagerWrapper; 392 } 393 394 @VisibleForTesting setPackageManagerWrapper(PackageManagerWrapper packageManagerWrapper)395 void setPackageManagerWrapper(PackageManagerWrapper packageManagerWrapper) { 396 mPackageManagerWrapper = packageManagerWrapper; 397 } 398 399 @VisibleForTesting setDefaultIcon(Drawable icon)400 void setDefaultIcon(Drawable icon) { 401 mDefaultIcon = icon; 402 } 403 } 404