blob: e7fb89c6197e7c1f21685b3274de72005aaf9282 [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.car.cluster;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.app.IProcessObserver;
import android.app.TaskStackListener;
import android.content.ComponentName;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Top activity monitor, allows listeners to be notified when a new activity comes to the foreground
* on a particular device.
*/
public class ActivityMonitor {
private static final String TAG = "Cluster.ActivityMonitor";
/**
* Listener of activity changes
*/
public interface ActivityListener {
/**
* Invoked when a new activity becomes the top activity on a particular display.
*/
void onTopActivityChanged(int displayId, @Nullable ComponentName activity);
}
private IActivityManager mActivityManager;
// Listeners of top activity changes, indexed by the displayId they are interested on.
private final Map<Integer, Set<ActivityListener>> mListeners = new HashMap<>();
private final Handler mHandler = new Handler();
private final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
notifyTopActivities();
}
@Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { }
@Override
public void onProcessDied(int pid, int uid) {
notifyTopActivities();
}
};
private final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
Log.i(TAG, "onTaskStackChanged");
notifyTopActivities();
}
};
/**
* Registers a new listener to receive activity updates on a particular display
*
* @param displayId identifier of the display to monitor
* @param listener listener to be notified
*/
public void addListener(int displayId, ActivityListener listener) {
mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).add(listener);
}
/**
* Unregisters a listener previously registered with {@link #addListener(int, ActivityListener)}
*/
public void removeListener(int displayId, ActivityListener listener) {
mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).remove(listener);
}
/**
* Starts monitoring activity changes. {@link #stop()} should be invoked to release resources.
*/
public void start() {
mActivityManager = ActivityManager.getService();
// Monitoring both listeners are necessary as there are cases where one listener cannot
// monitor activity change.
try {
mActivityManager.registerProcessObserver(mProcessObserver);
mActivityManager.registerTaskStackListener(mTaskStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Cannot register activity monitoring", e);
throw new RuntimeException(e);
}
notifyTopActivities();
}
/**
* Stops monitoring activity changes. Should be invoked when this monitor is not longer used.
*/
public void stop() {
if (mActivityManager == null) {
return;
}
try {
mActivityManager.unregisterProcessObserver(mProcessObserver);
mActivityManager.unregisterTaskStackListener(mTaskStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Cannot unregister activity monitoring. Ignoring", e);
}
mActivityManager = null;
}
/**
* Notifies listeners on changes of top activities. {@link ActivityManager} might trigger
* updates on threads different than UI.
*/
private void notifyTopActivities() {
mHandler.post(() -> {
try {
List<StackInfo> infos = mActivityManager.getAllStackInfos();
for (StackInfo info : infos) {
Set<ActivityListener> listeners = mListeners.get(info.displayId);
if (listeners != null && !listeners.isEmpty()) {
for (ActivityListener listener : listeners) {
listener.onTopActivityChanged(info.displayId, info.topActivity);
}
}
}
} catch (RemoteException e) {
Log.e(TAG, "Cannot getTasks", e);
}
});
}
}