1 /* 2 * Copyright (C) 2016 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 package com.android.car; 17 18 import android.app.ActivityManager; 19 import android.app.ActivityManager.StackInfo; 20 import android.app.IActivityManager; 21 import android.app.IProcessObserver; 22 import android.app.TaskStackListener; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.os.RemoteException; 31 import android.os.UserHandle; 32 import android.util.ArrayMap; 33 import android.util.ArraySet; 34 import android.util.Log; 35 import android.util.Pair; 36 import android.util.SparseArray; 37 38 import java.io.PrintWriter; 39 import java.util.Arrays; 40 import java.util.LinkedList; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Objects; 44 import java.util.Set; 45 46 /** 47 * Service to monitor AMS for new Activity or Service launching. 48 */ 49 public class SystemActivityMonitoringService implements CarServiceBase { 50 51 /** 52 * Container to hold info on top task in an Activity stack 53 */ 54 public static class TopTaskInfoContainer { 55 public final ComponentName topActivity; 56 public final int taskId; 57 public final StackInfo stackInfo; 58 59 private TopTaskInfoContainer(ComponentName topActivity, int taskId, StackInfo stackInfo) { 60 this.topActivity = topActivity; 61 this.taskId = taskId; 62 this.stackInfo = stackInfo; 63 } 64 65 public boolean isMatching(TopTaskInfoContainer taskInfo) { 66 return taskInfo != null 67 && Objects.equals(this.topActivity, taskInfo.topActivity) 68 && this.taskId == taskInfo.taskId 69 && this.stackInfo.userId == taskInfo.stackInfo.userId; 70 } 71 72 @Override 73 public String toString() { 74 return String.format( 75 "TaskInfoContainer [topActivity=%s, taskId=%d, stackId=%d, userId=%d", 76 topActivity, taskId, stackInfo.stackId, stackInfo.userId); 77 } 78 } 79 80 public interface ActivityLaunchListener { 81 /** 82 * Notify launch of activity. 83 * @param topTask Task information for what is currently launched. 84 */ 85 void onActivityLaunch(TopTaskInfoContainer topTask); 86 } 87 88 private final Context mContext; 89 private final IActivityManager mAm; 90 private final ProcessObserver mProcessObserver; 91 private final TaskListener mTaskListener; 92 93 private final HandlerThread mMonitorHandlerThread; 94 private final ActivityMonitorHandler mHandler; 95 96 /** K: stack id, V: top task */ 97 private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>(); 98 /** K: uid, V : list of pid */ 99 private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>(); 100 private int mFocusedStackId = -1; 101 102 /** 103 * Temporary container to dispatch tasks for onActivityLaunch. Only used in handler thread. 104 * can be accessed without lock. */ 105 private final List<TopTaskInfoContainer> mTasksToDispatch = new LinkedList<>(); 106 private ActivityLaunchListener mActivityLaunchListener; 107 108 public SystemActivityMonitoringService(Context context) { 109 mContext = context; 110 mMonitorHandlerThread = new HandlerThread(CarLog.TAG_AM); 111 mMonitorHandlerThread.start(); 112 mHandler = new ActivityMonitorHandler(mMonitorHandlerThread.getLooper()); 113 mProcessObserver = new ProcessObserver(); 114 mTaskListener = new TaskListener(); 115 mAm = ActivityManager.getService(); 116 // Monitoring both listeners are necessary as there are cases where one listener cannot 117 // monitor activity change. 118 try { 119 mAm.registerProcessObserver(mProcessObserver); 120 mAm.registerTaskStackListener(mTaskListener); 121 } catch (RemoteException e) { 122 Log.e(CarLog.TAG_AM, "cannot register activity monitoring", e); 123 throw new RuntimeException(e); 124 } 125 updateTasks(); 126 } 127 128 @Override 129 public void init() { 130 } 131 132 @Override 133 public void release() { 134 } 135 136 @Override 137 public void dump(PrintWriter writer) { 138 writer.println("*SystemActivityMonitoringService*"); 139 writer.println(" Top Tasks:"); 140 synchronized (this) { 141 for (int i = 0; i < mTopTasks.size(); i++) { 142 TopTaskInfoContainer info = mTopTasks.valueAt(i); 143 if (info != null) { 144 writer.println(info); 145 } 146 } 147 writer.println(" Foregroud uid-pids:"); 148 for (Integer key : mForegroundUidPids.keySet()) { 149 Set<Integer> pids = mForegroundUidPids.get(key); 150 if (pids == null) { 151 continue; 152 } 153 writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray())); 154 } 155 writer.println(" focused stack:" + mFocusedStackId); 156 } 157 } 158 159 /** 160 * Block the current task: Launch new activity with given Intent and finish the current task. 161 * @param currentTask task to finish 162 * @param newActivityIntent Intent for new Activity 163 */ 164 public void blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) { 165 mHandler.requestBlockActivity(currentTask, newActivityIntent); 166 } 167 168 public List<TopTaskInfoContainer> getTopTasks() { 169 LinkedList<TopTaskInfoContainer> tasks = new LinkedList<>(); 170 synchronized (this) { 171 for (int i = 0; i < mTopTasks.size(); i++) { 172 tasks.add(mTopTasks.valueAt(i)); 173 } 174 } 175 return tasks; 176 } 177 178 public boolean isInForeground(int pid, int uid) { 179 synchronized (this) { 180 Set<Integer> pids = mForegroundUidPids.get(uid); 181 if (pids == null) { 182 return false; 183 } 184 if (pids.contains(pid)) { 185 return true; 186 } 187 } 188 return false; 189 } 190 191 /** 192 * Attempts to restart a task. 193 * 194 * <p>Restarts a task by sending an empty intent with flag 195 * {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} to its root activity. If the task does not exist, 196 * do nothing. 197 * 198 * @param taskId id of task to be restarted. 199 */ 200 public void restartTask(int taskId) { 201 String rootActivityName = null; 202 int userId = 0; 203 try { 204 findRootActivityName: 205 for (StackInfo info : mAm.getAllStackInfos()) { 206 for (int i = 0; i < info.taskIds.length; i++) { 207 if (info.taskIds[i] == taskId) { 208 rootActivityName = info.taskNames[i]; 209 userId = info.userId; 210 if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) { 211 Log.d(CarLog.TAG_AM, "Root activity is " + rootActivityName); 212 Log.d(CarLog.TAG_AM, "User id is " + userId); 213 } 214 // Break out of nested loop. 215 break findRootActivityName; 216 } 217 } 218 } 219 } catch (RemoteException e) { 220 Log.e(CarLog.TAG_AM, "Could not get stack info", e); 221 return; 222 } 223 224 if (rootActivityName == null) { 225 Log.e(CarLog.TAG_AM, "Could not find root activity with task id " + taskId); 226 return; 227 } 228 229 Intent rootActivityIntent = new Intent(); 230 rootActivityIntent.setComponent(ComponentName.unflattenFromString(rootActivityName)); 231 // Clear the task the root activity is running in and start it in a new task. 232 // Effectively restart root activity. 233 rootActivityIntent.addFlags( 234 Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 235 236 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 237 Log.i(CarLog.TAG_AM, "restarting root activity with user id " + userId); 238 } 239 mContext.startActivityAsUser(rootActivityIntent, new UserHandle(userId)); 240 } 241 242 public void registerActivityLaunchListener(ActivityLaunchListener listener) { 243 synchronized (this) { 244 mActivityLaunchListener = listener; 245 } 246 } 247 248 private void updateTasks() { 249 List<StackInfo> infos; 250 try { 251 infos = mAm.getAllStackInfos(); 252 } catch (RemoteException e) { 253 Log.e(CarLog.TAG_AM, "cannot getTasks", e); 254 return; 255 } 256 int focusedStackId = -1; 257 try { 258 // TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the 259 // synchronized block below based on this new API. 260 final StackInfo focusedStackInfo = mAm.getFocusedStackInfo(); 261 if (focusedStackInfo != null) { 262 focusedStackId = focusedStackInfo.stackId; 263 } 264 } catch (RemoteException e) { 265 Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e); 266 return; 267 } 268 mTasksToDispatch.clear(); 269 ActivityLaunchListener listener; 270 synchronized (this) { 271 listener = mActivityLaunchListener; 272 for (StackInfo info : infos) { 273 int stackId = info.stackId; 274 if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown 275 mTopTasks.remove(stackId); 276 continue; 277 } 278 TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer( 279 info.topActivity, info.taskIds[info.taskIds.length - 1], info); 280 TopTaskInfoContainer currentTopTaskInfo = mTopTasks.get(stackId); 281 282 // if a new task is added to stack or focused stack changes, should notify 283 if (currentTopTaskInfo == null || 284 !currentTopTaskInfo.isMatching(newTopTaskInfo) || 285 (focusedStackId == stackId && focusedStackId != mFocusedStackId)) { 286 mTopTasks.put(stackId, newTopTaskInfo); 287 mTasksToDispatch.add(newTopTaskInfo); 288 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 289 Log.i(CarLog.TAG_AM, "New top task: " + newTopTaskInfo); 290 } 291 } 292 } 293 mFocusedStackId = focusedStackId; 294 } 295 if (listener != null) { 296 for (TopTaskInfoContainer topTask : mTasksToDispatch) { 297 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 298 Log.i(CarLog.TAG_AM, "activity launched:" + topTask.toString()); 299 } 300 listener.onActivityLaunch(topTask); 301 } 302 } 303 } 304 305 public StackInfo getFocusedStackForTopActivity(ComponentName activity) { 306 StackInfo focusedStack; 307 try { 308 focusedStack = mAm.getFocusedStackInfo(); 309 } catch (RemoteException e) { 310 Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e); 311 return null; 312 } 313 if (focusedStack.taskNames.length == 0) { // nothing in focused stack 314 return null; 315 } 316 ComponentName topActivity = ComponentName.unflattenFromString( 317 focusedStack.taskNames[focusedStack.taskNames.length - 1]); 318 if (topActivity.equals(activity)) { 319 return focusedStack; 320 } else { 321 return null; 322 } 323 } 324 325 private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { 326 synchronized (this) { 327 if (foregroundActivities) { 328 Set<Integer> pids = mForegroundUidPids.get(uid); 329 if (pids == null) { 330 pids = new ArraySet<Integer>(); 331 mForegroundUidPids.put(uid, pids); 332 } 333 pids.add(pid); 334 } else { 335 doHandlePidGoneLocked(pid, uid); 336 } 337 } 338 } 339 340 private void handleProcessDied(int pid, int uid) { 341 synchronized (this) { 342 doHandlePidGoneLocked(pid, uid); 343 } 344 } 345 346 private void doHandlePidGoneLocked(int pid, int uid) { 347 Set<Integer> pids = mForegroundUidPids.get(uid); 348 if (pids != null) { 349 pids.remove(pid); 350 if (pids.isEmpty()) { 351 mForegroundUidPids.remove(uid); 352 } 353 } 354 } 355 356 /** 357 * block the current task with the provided new activity. 358 */ 359 private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) { 360 mContext.startActivityAsUser(newActivityIntent, 361 new UserHandle(currentTask.stackInfo.userId)); 362 // Now make stack with new activity focused. 363 findTaskAndGrantFocus(newActivityIntent.getComponent()); 364 } 365 366 private void findTaskAndGrantFocus(ComponentName activity) { 367 List<StackInfo> infos; 368 try { 369 infos = mAm.getAllStackInfos(); 370 } catch (RemoteException e) { 371 Log.e(CarLog.TAG_AM, "cannot getTasks", e); 372 return; 373 } 374 for (StackInfo info : infos) { 375 if (info.taskNames.length == 0) { 376 continue; 377 } 378 ComponentName topActivity = ComponentName.unflattenFromString( 379 info.taskNames[info.taskNames.length - 1]); 380 if (activity.equals(topActivity)) { 381 try { 382 mAm.setFocusedStack(info.stackId); 383 } catch (RemoteException e) { 384 Log.e(CarLog.TAG_AM, "cannot setFocusedStack to stack:" + info.stackId, e); 385 } 386 return; 387 } 388 } 389 Log.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity); 390 } 391 392 private class ProcessObserver extends IProcessObserver.Stub { 393 @Override 394 public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { 395 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 396 Log.i(CarLog.TAG_AM, 397 String.format("onForegroundActivitiesChanged uid %d pid %d fg %b", 398 uid, pid, foregroundActivities)); 399 } 400 mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities); 401 } 402 403 @Override 404 public void onProcessDied(int pid, int uid) { 405 mHandler.requestProcessDied(pid, uid); 406 } 407 } 408 409 private class TaskListener extends TaskStackListener { 410 @Override 411 public void onTaskStackChanged() { 412 if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) { 413 Log.i(CarLog.TAG_AM, "onTaskStackChanged"); 414 } 415 mHandler.requestUpdatingTask(); 416 } 417 } 418 419 private class ActivityMonitorHandler extends Handler { 420 private static final int MSG_UPDATE_TASKS = 0; 421 private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1; 422 private static final int MSG_PROCESS_DIED = 2; 423 private static final int MSG_BLOCK_ACTIVITY = 3; 424 425 private ActivityMonitorHandler(Looper looper) { 426 super(looper); 427 } 428 429 private void requestUpdatingTask() { 430 Message msg = obtainMessage(MSG_UPDATE_TASKS); 431 sendMessage(msg); 432 } 433 434 private void requestForegroundActivitiesChanged(int pid, int uid, 435 boolean foregroundActivities) { 436 Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid, 437 Boolean.valueOf(foregroundActivities)); 438 sendMessage(msg); 439 } 440 441 private void requestProcessDied(int pid, int uid) { 442 Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid); 443 sendMessage(msg); 444 } 445 446 private void requestBlockActivity(TopTaskInfoContainer currentTask, 447 Intent newActivityIntent) { 448 Message msg = obtainMessage(MSG_BLOCK_ACTIVITY, 449 new Pair<TopTaskInfoContainer, Intent>(currentTask, newActivityIntent)); 450 sendMessage(msg); 451 } 452 453 @Override 454 public void handleMessage(Message msg) { 455 switch (msg.what) { 456 case MSG_UPDATE_TASKS: 457 updateTasks(); 458 break; 459 case MSG_FOREGROUND_ACTIVITIES_CHANGED: 460 handleForegroundActivitiesChanged(msg.arg1, msg.arg2, (Boolean) msg.obj); 461 updateTasks(); 462 break; 463 case MSG_PROCESS_DIED: 464 handleProcessDied(msg.arg1, msg.arg2); 465 break; 466 case MSG_BLOCK_ACTIVITY: 467 Pair<TopTaskInfoContainer, Intent> pair = 468 (Pair<TopTaskInfoContainer, Intent>) msg.obj; 469 handleBlockActivity(pair.first, pair.second); 470 break; 471 } 472 } 473 } 474 } 475