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 static final boolean DBG = false;
     89 
     90     private static final int NUM_MAX_TASK_TO_FETCH = 10;
     91 
     92     private final Context mContext;
     93     private final IActivityManager mAm;
     94     private final ProcessObserver mProcessObserver;
     95     private final TaskListener mTaskListener;
     96 
     97     private final HandlerThread mMonitorHandlerThread;
     98     private final ActivityMonitorHandler mHandler;
     99 
    100     /** K: stack id, V: top task */
    101     private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
    102     /** K: uid, V : list of pid */
    103     private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
    104     private int mFocusedStackId = -1;
    105 
    106     /**
    107      * Temporary container to dispatch tasks for onActivityLaunch. Only used in handler thread.
    108      * can be accessed without lock. */
    109     private final List<TopTaskInfoContainer> mTasksToDispatch = new LinkedList<>();
    110     private ActivityLaunchListener mActivityLaunchListener;
    111 
    112     public SystemActivityMonitoringService(Context context) {
    113         mContext = context;
    114         mMonitorHandlerThread = new HandlerThread(CarLog.TAG_AM);
    115         mMonitorHandlerThread.start();
    116         mHandler = new ActivityMonitorHandler(mMonitorHandlerThread.getLooper());
    117         mProcessObserver = new ProcessObserver();
    118         mTaskListener = new TaskListener();
    119         mAm = ActivityManager.getService();
    120         // Monitoring both listeners are necessary as there are cases where one listener cannot
    121         // monitor activity change.
    122         try {
    123             mAm.registerProcessObserver(mProcessObserver);
    124             mAm.registerTaskStackListener(mTaskListener);
    125         } catch (RemoteException e) {
    126             Log.e(CarLog.TAG_AM, "cannot register activity monitoring", e);
    127             throw new RuntimeException(e);
    128         }
    129         updateTasks();
    130     }
    131 
    132     @Override
    133     public void init() {
    134     }
    135 
    136     @Override
    137     public void release() {
    138     }
    139 
    140     @Override
    141     public void dump(PrintWriter writer) {
    142         writer.println("*SystemActivityMonitoringService*");
    143         writer.println(" Top Tasks:");
    144         synchronized (this) {
    145             for (int i = 0; i < mTopTasks.size(); i++) {
    146                 TopTaskInfoContainer info = mTopTasks.valueAt(i);
    147                 if (info != null) {
    148                     writer.println(info);
    149                 }
    150             }
    151             writer.println(" Foregroud uid-pids:");
    152             for (Integer key : mForegroundUidPids.keySet()) {
    153                 Set<Integer> pids = mForegroundUidPids.get(key);
    154                 if (pids == null) {
    155                     continue;
    156                 }
    157                 writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray()));
    158             }
    159             writer.println(" focused stack:" + mFocusedStackId);
    160         }
    161     }
    162 
    163     /**
    164      * Block the current task: Launch new activity with given Intent and finish the current task.
    165      * @param currentTask task to finish
    166      * @param newActivityIntent Intent for new Activity
    167      */
    168     public void blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
    169         mHandler.requestBlockActivity(currentTask, newActivityIntent);
    170     }
    171 
    172     public List<TopTaskInfoContainer> getTopTasks() {
    173         LinkedList<TopTaskInfoContainer> tasks = new LinkedList<>();
    174         synchronized (this) {
    175             for (int i = 0; i < mTopTasks.size(); i++) {
    176                 tasks.add(mTopTasks.valueAt(i));
    177             }
    178         }
    179         return tasks;
    180     }
    181 
    182     public boolean isInForeground(int pid, int uid) {
    183         synchronized (this) {
    184             Set<Integer> pids = mForegroundUidPids.get(uid);
    185             if (pids == null) {
    186                 return false;
    187             }
    188             if (pids.contains(pid)) {
    189                 return true;
    190             }
    191         }
    192         return false;
    193     }
    194 
    195     public void registerActivityLaunchListener(ActivityLaunchListener listener) {
    196         synchronized (this) {
    197             mActivityLaunchListener = listener;
    198         }
    199     }
    200 
    201     private void updateTasks() {
    202         List<StackInfo> infos;
    203         try {
    204             infos = mAm.getAllStackInfos();
    205         } catch (RemoteException e) {
    206             Log.e(CarLog.TAG_AM, "cannot getTasks", e);
    207             return;
    208         }
    209         int focusedStackId = -1;
    210         try {
    211             focusedStackId = mAm.getFocusedStackId();
    212         } catch (RemoteException e) {
    213             Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
    214             return;
    215         }
    216         mTasksToDispatch.clear();
    217         ActivityLaunchListener listener;
    218         synchronized (this) {
    219             listener = mActivityLaunchListener;
    220             for (StackInfo info : infos) {
    221                 int stackId = info.stackId;
    222                 if (info.taskNames.length == 0 || !info.visible) { // empty stack or not shown
    223                     mTopTasks.remove(stackId);
    224                     continue;
    225                 }
    226                 TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
    227                         info.topActivity, info.taskIds[info.taskIds.length - 1], info);
    228                 TopTaskInfoContainer currentTopTaskInfo = mTopTasks.get(stackId);
    229 
    230                 // if a new task is added to stack or focused stack changes, should notify
    231                 if (currentTopTaskInfo == null ||
    232                         !currentTopTaskInfo.isMatching(newTopTaskInfo) ||
    233                         (focusedStackId == stackId && focusedStackId != mFocusedStackId)) {
    234                     mTopTasks.put(stackId, newTopTaskInfo);
    235                     mTasksToDispatch.add(newTopTaskInfo);
    236                     if (DBG) {
    237                         Log.i(CarLog.TAG_AM, "New top task: " + newTopTaskInfo);
    238                     }
    239                 }
    240             }
    241             mFocusedStackId = focusedStackId;
    242         }
    243         if (listener != null) {
    244             for (TopTaskInfoContainer topTask : mTasksToDispatch) {
    245                 if (DBG) {
    246                     Log.i(CarLog.TAG_AM, "activity launched:" + topTask.toString());
    247                 }
    248                 listener.onActivityLaunch(topTask);
    249             }
    250         }
    251     }
    252 
    253     public StackInfo getFocusedStackForTopActivity(ComponentName activity) {
    254         int focusedStackId = -1;
    255         try {
    256             focusedStackId = mAm.getFocusedStackId();
    257         } catch (RemoteException e) {
    258             Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
    259             return null;
    260         }
    261         StackInfo focusedStack;
    262         try {
    263             focusedStack = mAm.getStackInfo(focusedStackId);
    264         } catch (RemoteException e) {
    265             Log.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
    266             return null;
    267         }
    268         if (focusedStack.taskNames.length == 0) { // nothing in focused stack
    269             return null;
    270         }
    271         ComponentName topActivity = ComponentName.unflattenFromString(
    272                 focusedStack.taskNames[focusedStack.taskNames.length - 1]);
    273         if (topActivity.equals(activity)) {
    274             return focusedStack;
    275         } else {
    276             return null;
    277         }
    278     }
    279 
    280     private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
    281         synchronized (this) {
    282             if (foregroundActivities) {
    283                 Set<Integer> pids = mForegroundUidPids.get(uid);
    284                 if (pids == null) {
    285                     pids = new ArraySet<Integer>();
    286                     mForegroundUidPids.put(uid, pids);
    287                 }
    288                 pids.add(pid);
    289             } else {
    290                 doHandlePidGoneLocked(pid, uid);
    291             }
    292         }
    293     }
    294 
    295     private void handleProcessDied(int pid, int uid) {
    296         synchronized (this) {
    297             doHandlePidGoneLocked(pid, uid);
    298         }
    299     }
    300 
    301     private void doHandlePidGoneLocked(int pid, int uid) {
    302         Set<Integer> pids = mForegroundUidPids.get(uid);
    303         if (pids != null) {
    304             pids.remove(pid);
    305             if (pids.isEmpty()) {
    306                 mForegroundUidPids.remove(uid);
    307             }
    308         }
    309     }
    310 
    311     private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
    312         Log.i(CarLog.TAG_AM, String.format("stopping activity %s with taskid:%d",
    313                 currentTask.topActivity, currentTask.taskId));
    314         // Put launcher in the activity stack, so that we have something safe to show after the
    315         // block activity finishes.
    316         Intent launcherIntent = new Intent();
    317         launcherIntent.setComponent(ComponentName.unflattenFromString(
    318                 mContext.getString(R.string.defaultHomeActivity)));
    319         mContext.startActivity(launcherIntent);
    320 
    321         newActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    322 
    323         mContext.startActivityAsUser(newActivityIntent,
    324                 new UserHandle(currentTask.stackInfo.userId));
    325         // now make stack with new activity focused.
    326         findTaskAndGrantFocus(newActivityIntent.getComponent());
    327         try {
    328             mAm.removeTask(currentTask.taskId);
    329         } catch (RemoteException e) {
    330             Log.w(CarLog.TAG_AM, "cannot remove task:" + currentTask.taskId, e);
    331         }
    332     }
    333 
    334     private void findTaskAndGrantFocus(ComponentName activity) {
    335         List<StackInfo> infos;
    336         try {
    337             infos = mAm.getAllStackInfos();
    338         } catch (RemoteException e) {
    339             Log.e(CarLog.TAG_AM, "cannot getTasks", e);
    340             return;
    341         }
    342         for (StackInfo info : infos) {
    343             if (info.taskNames.length == 0) {
    344                 continue;
    345             }
    346             ComponentName topActivity = ComponentName.unflattenFromString(
    347                     info.taskNames[info.taskNames.length - 1]);
    348             if (activity.equals(topActivity)) {
    349                 try {
    350                     mAm.setFocusedStack(info.stackId);
    351                 } catch (RemoteException e) {
    352                     Log.e(CarLog.TAG_AM, "cannot setFocusedStack to stack:" + info.stackId, e);
    353                 }
    354                 return;
    355             }
    356         }
    357         Log.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity);
    358     }
    359 
    360     private class ProcessObserver extends IProcessObserver.Stub {
    361         @Override
    362         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
    363             if (DBG) {
    364                 Log.i(CarLog.TAG_AM,
    365                         String.format("onForegroundActivitiesChanged uid %d pid %d fg %b",
    366                     uid, pid, foregroundActivities));
    367             }
    368             mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities);
    369         }
    370 
    371         @Override
    372         public void onProcessDied(int pid, int uid) {
    373             mHandler.requestProcessDied(pid, uid);
    374         }
    375     }
    376 
    377     private class TaskListener extends TaskStackListener {
    378         @Override
    379         public void onTaskStackChanged() {
    380             if (DBG) {
    381                 Log.i(CarLog.TAG_AM, "onTaskStackChanged");
    382             }
    383             mHandler.requestUpdatingTask();
    384         }
    385     }
    386 
    387     private class ActivityMonitorHandler extends Handler {
    388         private static final int MSG_UPDATE_TASKS = 0;
    389         private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1;
    390         private static final int MSG_PROCESS_DIED = 2;
    391         private static final int MSG_BLOCK_ACTIVITY = 3;
    392 
    393         private ActivityMonitorHandler(Looper looper) {
    394             super(looper);
    395         }
    396 
    397         private void requestUpdatingTask() {
    398             Message msg = obtainMessage(MSG_UPDATE_TASKS);
    399             sendMessage(msg);
    400         }
    401 
    402         private void requestForegroundActivitiesChanged(int pid, int uid,
    403                 boolean foregroundActivities) {
    404             Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid,
    405                     Boolean.valueOf(foregroundActivities));
    406             sendMessage(msg);
    407         }
    408 
    409         private void requestProcessDied(int pid, int uid) {
    410             Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid);
    411             sendMessage(msg);
    412         }
    413 
    414         private void requestBlockActivity(TopTaskInfoContainer currentTask,
    415                 Intent newActivityIntent) {
    416             Message msg = obtainMessage(MSG_BLOCK_ACTIVITY,
    417                     new Pair<TopTaskInfoContainer, Intent>(currentTask, newActivityIntent));
    418             sendMessage(msg);
    419         }
    420 
    421         @Override
    422         public void handleMessage(Message msg) {
    423             switch (msg.what) {
    424                 case MSG_UPDATE_TASKS:
    425                     updateTasks();
    426                     break;
    427                 case MSG_FOREGROUND_ACTIVITIES_CHANGED:
    428                     handleForegroundActivitiesChanged(msg.arg1, msg.arg2, (Boolean) msg.obj);
    429                     updateTasks();
    430                     break;
    431                 case MSG_PROCESS_DIED:
    432                     handleProcessDied(msg.arg1, msg.arg2);
    433                     break;
    434                 case MSG_BLOCK_ACTIVITY:
    435                     Pair<TopTaskInfoContainer, Intent> pair =
    436                         (Pair<TopTaskInfoContainer, Intent>) msg.obj;
    437                     handleBlockActivity(pair.first, pair.second);
    438                     break;
    439             }
    440         }
    441     }
    442 }
    443