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