blob: 897fde381f93a240f4f2058b2d8a1354d5e58b3b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car;
import static com.android.car.pm.CarPackageManagerService.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityManager;
import android.app.IProcessObserver;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Service to monitor AMS for new Activity or Service launching.
*/
public class SystemActivityMonitoringService implements CarServiceBase {
/**
* Container to hold info on top task in an Activity stack
*/
public static class TopTaskInfoContainer {
public final ComponentName topActivity;
public final int taskId;
public final int displayId;
public final int position;
public final RootTaskInfo taskInfo;
private TopTaskInfoContainer(ComponentName topActivity, int taskId,
int displayId, int position, RootTaskInfo taskInfo) {
this.topActivity = topActivity;
this.taskId = taskId;
this.displayId = displayId;
this.position = position;
this.taskInfo = taskInfo;
}
public boolean isMatching(TopTaskInfoContainer taskInfo) {
return taskInfo != null
&& Objects.equals(this.topActivity, taskInfo.topActivity)
&& this.taskId == taskInfo.taskId
&& this.displayId == taskInfo.displayId
&& this.position == taskInfo.position
&& this.taskInfo.userId == taskInfo.taskInfo.userId;
}
@Override
public String toString() {
return String.format(
"TaskInfoContainer [topActivity=%s, taskId=%d, stackId=%d, userId=%d, "
+ "displayId=%d, position=%d",
topActivity, taskId, taskInfo.taskId, taskInfo.userId, displayId, position);
}
}
public interface ActivityLaunchListener {
/**
* Notify launch of activity.
* @param topTask Task information for what is currently launched.
*/
void onActivityLaunch(TopTaskInfoContainer topTask);
}
private static final int INVALID_STACK_ID = -1;
private final Context mContext;
private final IActivityManager mAm;
private final ProcessObserver mProcessObserver;
private final TaskListener mTaskListener;
private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread(
getClass().getSimpleName());
private final ActivityMonitorHandler mHandler = new ActivityMonitorHandler(
mMonitorHandlerThread.getLooper(), this);
private final Object mLock = new Object();
/** K: display id, V: top task */
@GuardedBy("mLock")
private final SparseArray<TopTaskInfoContainer> mTopTasks = new SparseArray<>();
/** K: uid, V : list of pid */
@GuardedBy("mLock")
private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
@GuardedBy("mLock")
private ActivityLaunchListener mActivityLaunchListener;
public SystemActivityMonitoringService(Context context) {
mContext = context;
mProcessObserver = new ProcessObserver();
mTaskListener = new TaskListener();
mAm = ActivityManager.getService();
}
@Override
public void init() {
// Monitoring both listeners are necessary as there are cases where one listener cannot
// monitor activity change.
try {
mAm.registerProcessObserver(mProcessObserver);
mAm.registerTaskStackListener(mTaskListener);
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot register activity monitoring", e);
throw new RuntimeException(e);
}
updateTasks();
}
@Override
public void release() {
try {
mAm.unregisterProcessObserver(mProcessObserver);
mAm.unregisterTaskStackListener(mTaskListener);
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "Failed to unregister listeners", e);
}
}
@Override
public void dump(IndentingPrintWriter writer) {
writer.println("*SystemActivityMonitoringService*");
writer.println(" Top Tasks per display:");
synchronized (mLock) {
for (int i = 0; i < mTopTasks.size(); i++) {
int displayId = mTopTasks.keyAt(i);
TopTaskInfoContainer info = mTopTasks.valueAt(i);
if (info != null) {
writer.println("display id " + displayId + ": " + info);
}
}
writer.println(" Foreground uid-pids:");
for (Integer key : mForegroundUidPids.keySet()) {
Set<Integer> pids = mForegroundUidPids.get(key);
if (pids == null) {
continue;
}
writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray()));
}
}
}
/**
* Block the current task: Launch new activity with given Intent and finish the current task.
* @param currentTask task to finish
* @param newActivityIntent Intent for new Activity
*/
public void blockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
mHandler.requestBlockActivity(currentTask, newActivityIntent);
}
public List<TopTaskInfoContainer> getTopTasks() {
LinkedList<TopTaskInfoContainer> tasks = new LinkedList<>();
synchronized (mLock) {
for (int i = 0; i < mTopTasks.size(); i++) {
TopTaskInfoContainer topTask = mTopTasks.valueAt(i);
if (topTask == null) {
Slog.e(CarLog.TAG_AM, "Top tasks contains null. Full content is: "
+ mTopTasks.toString());
continue;
}
tasks.add(topTask);
}
}
return tasks;
}
public boolean isInForeground(int pid, int uid) {
synchronized (mLock) {
Set<Integer> pids = mForegroundUidPids.get(uid);
if (pids == null) {
return false;
}
if (pids.contains(pid)) {
return true;
}
}
return false;
}
/**
* Attempts to restart a task.
*
* <p>Restarts a task by sending an empty intent with flag
* {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} to its root activity. If the task does not exist,
* do nothing.
*
* @param taskId id of task to be restarted.
*/
public void restartTask(int taskId) {
String rootActivityName = null;
int userId = 0;
try {
findRootActivityName:
for (RootTaskInfo info : mAm.getAllRootTaskInfos()) {
for (int i = 0; i < info.childTaskIds.length; i++) {
if (info.childTaskIds[i] == taskId) {
rootActivityName = info.childTaskNames[i];
userId = info.userId;
if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
Slog.d(CarLog.TAG_AM, "Root activity is " + rootActivityName);
Slog.d(CarLog.TAG_AM, "User id is " + userId);
}
// Break out of nested loop.
break findRootActivityName;
}
}
}
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "Could not get stack info", e);
return;
}
if (rootActivityName == null) {
Slog.e(CarLog.TAG_AM, "Could not find root activity with task id " + taskId);
return;
}
Intent rootActivityIntent = new Intent();
rootActivityIntent.setComponent(ComponentName.unflattenFromString(rootActivityName));
// Clear the task the root activity is running in and start it in a new task.
// Effectively restart root activity.
rootActivityIntent.addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
Slog.i(CarLog.TAG_AM, "restarting root activity with user id " + userId);
}
mContext.startActivityAsUser(rootActivityIntent, new UserHandle(userId));
}
public void registerActivityLaunchListener(ActivityLaunchListener listener) {
synchronized (mLock) {
mActivityLaunchListener = listener;
}
}
private void updateTasks() {
List<RootTaskInfo> infos;
try {
infos = mAm.getAllRootTaskInfos();
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot getTasks", e);
return;
}
if (infos == null) {
return;
}
int focusedStackId = INVALID_STACK_ID;
try {
// TODO(b/66955160): Someone on the Auto-team should probably re-work the code in the
// synchronized block below based on this new API.
final RootTaskInfo focusedTaskInfo = mAm.getFocusedRootTaskInfo();
if (focusedTaskInfo != null) {
focusedStackId = focusedTaskInfo.taskId;
}
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
return;
}
SparseArray<TopTaskInfoContainer> topTasks = new SparseArray<>();
ActivityLaunchListener listener;
synchronized (mLock) {
mTopTasks.clear();
listener = mActivityLaunchListener;
for (RootTaskInfo info : infos) {
int displayId = info.displayId;
if (info.childTaskNames.length == 0
|| !info.visible) { // empty stack or not shown
continue;
}
TopTaskInfoContainer newTopTaskInfo = new TopTaskInfoContainer(
info.topActivity, info.childTaskIds[info.childTaskIds.length - 1],
info.displayId, info.position, info);
TopTaskInfoContainer currentTopTaskInfo = topTasks.get(displayId);
if (currentTopTaskInfo == null ||
newTopTaskInfo.position > currentTopTaskInfo.position) {
topTasks.put(displayId, newTopTaskInfo);
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
Slog.i(CarLog.TAG_AM, "Updating top task to: " + newTopTaskInfo);
}
}
}
// Assuming displays remains the same.
for (int i = 0; i < topTasks.size(); i++) {
TopTaskInfoContainer topTask = topTasks.valueAt(i);
int displayId = topTasks.keyAt(i);
mTopTasks.put(displayId, topTask);
}
}
if (listener != null) {
for (int i = 0; i < topTasks.size(); i++) {
TopTaskInfoContainer topTask = topTasks.valueAt(i);
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
Slog.i(CarLog.TAG_AM, "Notifying about top task: " + topTask.toString());
}
listener.onActivityLaunch(topTask);
}
}
}
public RootTaskInfo getFocusedStackForTopActivity(ComponentName activity) {
RootTaskInfo focusedStack;
try {
focusedStack = mAm.getFocusedRootTaskInfo();
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot getFocusedStackId", e);
return null;
}
if (focusedStack.childTaskNames.length == 0) { // nothing in focused stack
return null;
}
ComponentName topActivity = ComponentName.unflattenFromString(
focusedStack.childTaskNames[focusedStack.childTaskNames.length - 1]);
if (topActivity.equals(activity)) {
return focusedStack;
} else {
return null;
}
}
private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
synchronized (mLock) {
if (foregroundActivities) {
Set<Integer> pids = mForegroundUidPids.get(uid);
if (pids == null) {
pids = new ArraySet<Integer>();
mForegroundUidPids.put(uid, pids);
}
pids.add(pid);
} else {
doHandlePidGoneLocked(pid, uid);
}
}
}
private void handleProcessDied(int pid, int uid) {
synchronized (mLock) {
doHandlePidGoneLocked(pid, uid);
}
}
private void doHandlePidGoneLocked(int pid, int uid) {
Set<Integer> pids = mForegroundUidPids.get(uid);
if (pids != null) {
pids.remove(pid);
if (pids.isEmpty()) {
mForegroundUidPids.remove(uid);
}
}
}
/**
* block the current task with the provided new activity.
*/
private void handleBlockActivity(TopTaskInfoContainer currentTask, Intent newActivityIntent) {
int displayId = newActivityIntent.getIntExtra(BLOCKING_INTENT_EXTRA_DISPLAY_ID,
Display.DEFAULT_DISPLAY);
if (Log.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
Slog.d(CarLog.TAG_AM, "Launching blocking activity on display: " + displayId);
}
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(displayId);
mContext.startActivityAsUser(newActivityIntent, options.toBundle(),
new UserHandle(currentTask.taskInfo.userId));
// Now make stack with new activity focused.
findTaskAndGrantFocus(newActivityIntent.getComponent());
}
private void findTaskAndGrantFocus(ComponentName activity) {
List<RootTaskInfo> infos;
try {
infos = mAm.getAllRootTaskInfos();
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot getTasks", e);
return;
}
for (RootTaskInfo info : infos) {
if (info.childTaskNames.length == 0) {
continue;
}
ComponentName topActivity = ComponentName.unflattenFromString(
info.childTaskNames[info.childTaskNames.length - 1]);
if (activity.equals(topActivity)) {
try {
mAm.setFocusedRootTask(info.taskId);
} catch (RemoteException e) {
Slog.e(CarLog.TAG_AM, "cannot setFocusedRootTask to task:" + info.taskId, e);
}
return;
}
}
Slog.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity);
}
private class ProcessObserver extends IProcessObserver.Stub {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
Slog.i(CarLog.TAG_AM,
String.format("onForegroundActivitiesChanged uid %d pid %d fg %b",
uid, pid, foregroundActivities));
}
mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities);
}
@Override
public void onForegroundServicesChanged(int pid, int uid, int fgServiceTypes) {
}
@Override
public void onProcessDied(int pid, int uid) {
mHandler.requestProcessDied(pid, uid);
}
}
private class TaskListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
if (Log.isLoggable(CarLog.TAG_AM, Log.INFO)) {
Slog.i(CarLog.TAG_AM, "onTaskStackChanged");
}
mHandler.requestUpdatingTask();
}
}
private static final class ActivityMonitorHandler extends Handler {
private static final String TAG = ActivityMonitorHandler.class.getSimpleName();
private static final int MSG_UPDATE_TASKS = 0;
private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1;
private static final int MSG_PROCESS_DIED = 2;
private static final int MSG_BLOCK_ACTIVITY = 3;
private final WeakReference<SystemActivityMonitoringService> mService;
private ActivityMonitorHandler(Looper looper, SystemActivityMonitoringService service) {
super(looper);
mService = new WeakReference<SystemActivityMonitoringService>(service);
}
private void requestUpdatingTask() {
Message msg = obtainMessage(MSG_UPDATE_TASKS);
sendMessage(msg);
}
private void requestForegroundActivitiesChanged(int pid, int uid,
boolean foregroundActivities) {
Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid,
Boolean.valueOf(foregroundActivities));
sendMessage(msg);
}
private void requestProcessDied(int pid, int uid) {
Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid);
sendMessage(msg);
}
private void requestBlockActivity(TopTaskInfoContainer currentTask,
Intent newActivityIntent) {
Message msg = obtainMessage(MSG_BLOCK_ACTIVITY,
new Pair<TopTaskInfoContainer, Intent>(currentTask, newActivityIntent));
sendMessage(msg);
}
@Override
public void handleMessage(Message msg) {
SystemActivityMonitoringService service = mService.get();
if (service == null) {
Slog.i(TAG, "handleMessage null service");
return;
}
switch (msg.what) {
case MSG_UPDATE_TASKS:
service.updateTasks();
break;
case MSG_FOREGROUND_ACTIVITIES_CHANGED:
service.handleForegroundActivitiesChanged(msg.arg1, msg.arg2,
(Boolean) msg.obj);
service.updateTasks();
break;
case MSG_PROCESS_DIED:
service.handleProcessDied(msg.arg1, msg.arg2);
break;
case MSG_BLOCK_ACTIVITY:
Pair<TopTaskInfoContainer, Intent> pair =
(Pair<TopTaskInfoContainer, Intent>) msg.obj;
service.handleBlockActivity(pair.first, pair.second);
break;
}
}
}
}