• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TopTaskInfoContainer(ComponentName topActivity, int taskId, StackInfo stackInfo)59         private TopTaskInfoContainer(ComponentName topActivity, int taskId, StackInfo stackInfo) {
60             this.topActivity = topActivity;
61             this.taskId = taskId;
62             this.stackInfo = stackInfo;
63         }
64 
isMatching(TopTaskInfoContainer taskInfo)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
toString()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          */
onActivityLaunch(TopTaskInfoContainer topTask)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 
SystemActivityMonitoringService(Context context)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
init()129     public void init() {
130     }
131 
132     @Override
release()133     public void release() {
134     }
135 
136     @Override
dump(PrintWriter writer)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      */
blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent)164     public void blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
165         mHandler.requestBlockActivity(currentTask, newActivityIntent);
166     }
167 
getTopTasks()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 
isInForeground(int pid, int uid)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      */
restartTask(int taskId)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 
registerActivityLaunchListener(ActivityLaunchListener listener)242     public void registerActivityLaunchListener(ActivityLaunchListener listener) {
243         synchronized (this) {
244             mActivityLaunchListener = listener;
245         }
246     }
247 
updateTasks()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 
getFocusedStackForTopActivity(ComponentName activity)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 
handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)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 
handleProcessDied(int pid, int uid)340     private void handleProcessDied(int pid, int uid) {
341         synchronized (this) {
342             doHandlePidGoneLocked(pid, uid);
343         }
344     }
345 
doHandlePidGoneLocked(int pid, int uid)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      */
handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent)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 
findTaskAndGrantFocus(ComponentName activity)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
onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)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
onProcessDied(int pid, int uid)404         public void onProcessDied(int pid, int uid) {
405             mHandler.requestProcessDied(pid, uid);
406         }
407     }
408 
409     private class TaskListener extends TaskStackListener {
410         @Override
onTaskStackChanged()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 
ActivityMonitorHandler(Looper looper)425         private ActivityMonitorHandler(Looper looper) {
426             super(looper);
427         }
428 
requestUpdatingTask()429         private void requestUpdatingTask() {
430             Message msg = obtainMessage(MSG_UPDATE_TASKS);
431             sendMessage(msg);
432         }
433 
requestForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)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 
requestProcessDied(int pid, int uid)441         private void requestProcessDied(int pid, int uid) {
442             Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid);
443             sendMessage(msg);
444         }
445 
requestBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent)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
handleMessage(Message msg)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