Home | History | Annotate | Download | only in car
      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