1 /* 2 * Copyright (C) 2021 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.am; 18 19 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; 20 import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID; 21 22 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 23 24 import android.app.ActivityManager; 25 import android.app.ActivityOptions; 26 import android.app.TaskInfo; 27 import android.car.Car; 28 import android.car.app.CarActivityManager; 29 import android.car.app.ICarActivityService; 30 import android.car.builtin.app.ActivityManagerHelper; 31 import android.car.builtin.app.TaskInfoHelper; 32 import android.car.builtin.content.ContextHelper; 33 import android.car.builtin.os.UserManagerHelper; 34 import android.car.builtin.util.Slogf; 35 import android.content.ComponentName; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.pm.PackageManager; 39 import android.os.Binder; 40 import android.os.Handler; 41 import android.os.HandlerThread; 42 import android.os.IBinder; 43 import android.os.RemoteException; 44 import android.os.UserHandle; 45 import android.util.ArrayMap; 46 import android.util.Log; 47 import android.view.Display; 48 49 import com.android.car.CarLog; 50 import com.android.car.CarServiceBase; 51 import com.android.car.CarServiceUtils; 52 import com.android.car.SystemActivityMonitoringService; 53 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 54 import com.android.car.internal.ICarServiceHelper; 55 import com.android.car.internal.util.IndentingPrintWriter; 56 import com.android.internal.annotations.GuardedBy; 57 import com.android.internal.annotations.VisibleForTesting; 58 59 import java.util.ArrayList; 60 import java.util.Collections; 61 import java.util.LinkedHashMap; 62 import java.util.List; 63 import java.util.Objects; 64 65 /** 66 * Service responsible for Activities in Car. 67 */ 68 public final class CarActivityService extends ICarActivityService.Stub 69 implements CarServiceBase { 70 71 private static final String TAG = CarLog.TAG_AM; 72 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 73 74 private final Context mContext; 75 76 private final Object mLock = new Object(); 77 78 @GuardedBy("mLock") 79 ICarServiceHelper mICarServiceHelper; 80 81 // LinkedHashMap is used instead of SparseXXX because a predictable iteration order is needed. 82 // The tasks here need be ordered as per their stack order. The stack order is maintained 83 // using a combination of onTaskAppeared and onTaskInfoChanged callbacks. 84 @GuardedBy("mLock") 85 private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mTasks = 86 new LinkedHashMap<>(); 87 @GuardedBy("mLock") 88 private final ArrayMap<IBinder, IBinder.DeathRecipient> mTokens = new ArrayMap<>(); 89 @GuardedBy("mLock") 90 private IBinder mCurrentMonitor; 91 92 public interface ActivityLaunchListener { 93 /** 94 * Notify launch of activity. 95 * 96 * @param topTask Task information for what is currently launched. 97 */ onActivityLaunch(TaskInfo topTask)98 void onActivityLaunch(TaskInfo topTask); 99 } 100 @GuardedBy("mLock") 101 private ActivityLaunchListener mActivityLaunchListener; 102 103 private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread( 104 SystemActivityMonitoringService.class.getSimpleName()); 105 private final Handler mHandler = new Handler(mMonitorHandlerThread.getLooper()); 106 CarActivityService(Context context)107 public CarActivityService(Context context) { 108 mContext = context; 109 } 110 111 @Override init()112 public void init() {} 113 114 @Override release()115 public void release() {} 116 117 /** 118 * Sets {@code ICarServiceHelper}. 119 */ setICarServiceHelper(ICarServiceHelper helper)120 public void setICarServiceHelper(ICarServiceHelper helper) { 121 synchronized (mLock) { 122 mICarServiceHelper = helper; 123 } 124 } 125 126 @Override setPersistentActivity(ComponentName activity, int displayId, int featureId)127 public int setPersistentActivity(ComponentName activity, int displayId, int featureId) throws 128 RemoteException { 129 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( 130 Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)) { 131 throw new SecurityException("Requires " + Car.PERMISSION_CONTROL_CAR_APP_LAUNCH); 132 } 133 int caller = getCaller(); 134 if (caller != UserManagerHelper.USER_SYSTEM && caller != ActivityManager.getCurrentUser()) { 135 return CarActivityManager.RESULT_INVALID_USER; 136 } 137 138 ICarServiceHelper helper; 139 synchronized (mLock) { 140 helper = mICarServiceHelper; 141 } 142 if (helper == null) { 143 throw new IllegalStateException("ICarServiceHelper isn't connected yet"); 144 } 145 return helper.setPersistentActivity(activity, displayId, featureId); 146 } 147 148 @VisibleForTesting getCaller()149 int getCaller() { // Non static for mocking. 150 return UserManagerHelper.getUserId(Binder.getCallingUid()); 151 } 152 registerActivityLaunchListener(ActivityLaunchListener listener)153 public void registerActivityLaunchListener(ActivityLaunchListener listener) { 154 synchronized (mLock) { 155 mActivityLaunchListener = listener; 156 } 157 } 158 159 @Override registerTaskMonitor(IBinder token)160 public void registerTaskMonitor(IBinder token) { 161 if (DBG) Slogf.d(TAG, "registerTaskMonitor: %s", token); 162 ensurePermission(); 163 164 IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { 165 @Override 166 public void binderDied() { 167 cleanUpToken(token); 168 } 169 }; 170 synchronized (mLock) { 171 try { 172 token.linkToDeath(deathRecipient, /* flags= */ 0); 173 } catch (RemoteException e) { 174 // 'token' owner might be dead already. 175 Slogf.e(TAG, "failed to linkToDeath: %s", token); 176 return; 177 } 178 mTokens.put(token, deathRecipient); 179 mCurrentMonitor = token; 180 // When new TaskOrganizer takes the control, it'll get the status of the whole tasks 181 // in the system again. So drops the old status. 182 mTasks.clear(); 183 } 184 } 185 ensurePermission()186 private void ensurePermission() { 187 if (mContext.checkCallingOrSelfPermission(MANAGE_ACTIVITY_TASKS) 188 != PackageManager.PERMISSION_GRANTED) { 189 throw new SecurityException("requires permission " + MANAGE_ACTIVITY_TASKS); 190 } 191 } 192 cleanUpToken(IBinder token)193 private void cleanUpToken(IBinder token) { 194 synchronized (mLock) { 195 if (mCurrentMonitor == token) { 196 mCurrentMonitor = null; 197 } 198 IBinder.DeathRecipient deathRecipient = mTokens.remove(token); 199 if (deathRecipient != null) { 200 token.unlinkToDeath(deathRecipient, /* flags= */ 0); 201 } 202 } 203 } 204 205 @Override onTaskAppeared(IBinder token, ActivityManager.RunningTaskInfo taskInfo)206 public void onTaskAppeared(IBinder token, ActivityManager.RunningTaskInfo taskInfo) { 207 if (DBG) { 208 Slogf.d(TAG, "onTaskAppeared: %s, %s", token, TaskInfoHelper.toString(taskInfo)); 209 } 210 ensurePermission(); 211 synchronized (mLock) { 212 if (!isAllowedToUpdateLocked(token)) { 213 return; 214 } 215 mTasks.put(taskInfo.taskId, taskInfo); 216 } 217 if (TaskInfoHelper.isVisible(taskInfo)) { 218 mHandler.post(() -> notifyActivityLaunch(taskInfo)); 219 } 220 } 221 notifyActivityLaunch(TaskInfo taskInfo)222 private void notifyActivityLaunch(TaskInfo taskInfo) { 223 ActivityLaunchListener listener; 224 synchronized (mLock) { 225 listener = mActivityLaunchListener; 226 } 227 if (listener != null) { 228 listener.onActivityLaunch(taskInfo); 229 } 230 } 231 232 @GuardedBy("mLock") isAllowedToUpdateLocked(IBinder token)233 private boolean isAllowedToUpdateLocked(IBinder token) { 234 if (mCurrentMonitor == token) { 235 return true; 236 } 237 // Fallback during no current Monitor exists. 238 boolean allowed = (mCurrentMonitor == null && mTokens.containsKey(token)); 239 if (!allowed) { 240 Slogf.w(TAG, "Report with the invalid token: %s", token); 241 } 242 return allowed; 243 } 244 245 @Override onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo)246 public void onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo) { 247 if (DBG) { 248 Slogf.d(TAG, "onTaskVanished: %s, %s", token, TaskInfoHelper.toString(taskInfo)); 249 } 250 ensurePermission(); 251 synchronized (mLock) { 252 if (!isAllowedToUpdateLocked(token)) { 253 return; 254 } 255 mTasks.remove(taskInfo.taskId); 256 } 257 } 258 259 @Override onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo)260 public void onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo) { 261 if (DBG) { 262 Slogf.d(TAG, "onTaskInfoChanged: %s, %s", token, TaskInfoHelper.toString(taskInfo)); 263 } 264 ensurePermission(); 265 synchronized (mLock) { 266 if (!isAllowedToUpdateLocked(token)) { 267 return; 268 } 269 // The key should be removed and added again so that it jumps to the front of the 270 // LinkedHashMap. 271 TaskInfo oldTaskInfo = mTasks.remove(taskInfo.taskId); 272 mTasks.put(taskInfo.taskId, taskInfo); 273 if ((oldTaskInfo == null || !TaskInfoHelper.isVisible(oldTaskInfo) 274 || !Objects.equals(oldTaskInfo.topActivity, taskInfo.topActivity)) 275 && TaskInfoHelper.isVisible(taskInfo)) { 276 mHandler.post(() -> notifyActivityLaunch(taskInfo)); 277 } 278 } 279 } 280 281 @Override unregisterTaskMonitor(IBinder token)282 public void unregisterTaskMonitor(IBinder token) { 283 if (DBG) Slogf.d(TAG, "unregisterTaskMonitor: %s", token); 284 ensurePermission(); 285 cleanUpToken(token); 286 } 287 288 /** 289 * Returns all the visible tasks. The order is not guaranteed. 290 */ 291 @Override getVisibleTasks()292 public List<ActivityManager.RunningTaskInfo> getVisibleTasks() { 293 ArrayList<ActivityManager.RunningTaskInfo> tasksToReturn = new ArrayList<>(); 294 synchronized (mLock) { 295 for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) { 296 // Activities launched in the private display or non-focusable display can't be 297 // focusable. So we just monitor all visible Activities/Tasks. 298 if (TaskInfoHelper.isVisible(taskInfo)) { 299 tasksToReturn.add(taskInfo); 300 } 301 } 302 } 303 // Reverse the order so that the resultant order is top to bottom. 304 Collections.reverse(tasksToReturn); 305 return tasksToReturn; 306 } 307 308 /** 309 * Attempts to restart a task. 310 * 311 * <p>Restarts a task by sending an empty intent with flag 312 * {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} to its root activity. If the task does not exist, do 313 * nothing. 314 * 315 * @param taskId id of task to be restarted. 316 */ restartTask(int taskId)317 public void restartTask(int taskId) { 318 TaskInfo task; 319 synchronized (mLock) { 320 task = mTasks.get(taskId); 321 } 322 if (task == null) { 323 Slogf.e(CarLog.TAG_AM, "Could not find root activity with task id " + taskId); 324 return; 325 } 326 327 Intent intent = (Intent) task.baseIntent.clone(); 328 // Clear the task the root activity is running in and start it in a new task. 329 // Effectively restart root activity. 330 intent.addFlags( 331 Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 332 333 int userId = TaskInfoHelper.getUserId(task); 334 if (Slogf.isLoggable(CarLog.TAG_AM, Log.INFO)) { 335 Slogf.i(CarLog.TAG_AM, "restarting root activity with user id " + userId); 336 } 337 mContext.startActivityAsUser(intent, UserHandle.of(userId)); 338 } 339 getTaskInfoForTopActivity(ComponentName activity)340 public TaskInfo getTaskInfoForTopActivity(ComponentName activity) { 341 synchronized (mLock) { 342 for (ActivityManager.RunningTaskInfo info : mTasks.values()) { 343 if (activity.equals(info.topActivity)) { 344 return info; 345 } 346 } 347 } 348 return null; 349 } 350 351 /** 352 * Block the current task: Launch new activity with given Intent and finish the current task. 353 * 354 * @param currentTask task to finish 355 * @param newActivityIntent Intent for new Activity 356 */ blockActivity(TaskInfo currentTask, Intent newActivityIntent)357 public void blockActivity(TaskInfo currentTask, Intent newActivityIntent) { 358 mHandler.post(() -> handleBlockActivity(currentTask, newActivityIntent)); 359 } 360 361 /** 362 * block the current task with the provided new activity. 363 */ handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent)364 private void handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent) { 365 int displayId = newActivityIntent.getIntExtra(BLOCKING_INTENT_EXTRA_DISPLAY_ID, 366 Display.DEFAULT_DISPLAY); 367 if (Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG)) { 368 Slogf.d(CarLog.TAG_AM, "Launching blocking activity on display: " + displayId); 369 } 370 371 ActivityOptions options = ActivityOptions.makeBasic(); 372 options.setLaunchDisplayId(displayId); 373 ContextHelper.startActivityAsUser(mContext, newActivityIntent, options.toBundle(), 374 UserHandle.of(TaskInfoHelper.getUserId(currentTask))); 375 // Now make stack with new activity focused. 376 findTaskAndGrantFocus(newActivityIntent.getComponent()); 377 } 378 findTaskAndGrantFocus(ComponentName activity)379 private void findTaskAndGrantFocus(ComponentName activity) { 380 TaskInfo taskInfo = getTaskInfoForTopActivity(activity); 381 if (taskInfo != null) { 382 ActivityManagerHelper.setFocusedRootTask(taskInfo.taskId); 383 return; 384 } 385 Slogf.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity); 386 } 387 388 @Override 389 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)390 public void dump(IndentingPrintWriter writer) { 391 writer.println("*CarActivityService*"); 392 writer.println(" Tasks:"); 393 synchronized (mLock) { 394 for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) { 395 writer.println(" " + TaskInfoHelper.toString(taskInfo)); 396 } 397 } 398 } 399 } 400