| /* |
| * 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.annotation.UiThread; |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager.RootTaskInfo; |
| import android.app.IActivityManager; |
| import android.app.IProcessObserver; |
| import android.app.TaskStackListener; |
| import android.content.ComponentName; |
| import android.os.Handler; |
| import android.os.Looper; |
| 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. |
| * |
| * As a quick check {@link #notifyTopActivities} is handed to the UI thread because it is triggered |
| * by {@link #mProcessObserver} and {@link #mTaskStackListener}, which may be called by background |
| * threads. |
| * |
| * {@link #start} and {@link #stop} should be called only by the UI thread to prevent possible NPEs. |
| */ |
| 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() { |
| /** |
| * Note: This function may sometimes be called from a background thread |
| */ |
| @Override |
| public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { |
| notifyTopActivities(); |
| } |
| |
| /** |
| * Note: This function may sometimes be called from a background thread |
| */ |
| @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() { |
| /** |
| * Note: This function may sometimes be called from a background thread |
| */ |
| @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. |
| * |
| * This method should be called on the UI thread. Otherwise, runtime exceptions may occur. |
| */ |
| @UiThread |
| 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. |
| * |
| * This method should be called on the UI thread. Otherwise, runtime exceptions may occur. |
| */ |
| @UiThread |
| public void stop() { |
| if (mActivityManager == null) { |
| return; |
| } |
| if (Looper.getMainLooper().getThread() != Thread.currentThread()) { |
| Log.w(TAG, "stop() is called on non-UI thread. May cause NPE"); |
| } |
| 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. |
| * |
| * Note: This method may sometimes be called by background threads, so it is synchronized on |
| * the UI thread with mHandler.post() |
| */ |
| private void notifyTopActivities() { |
| mHandler.post(() -> { |
| try { |
| // return if the activity monitor is no longer used |
| if (mActivityManager == null) { |
| return; |
| } |
| List<RootTaskInfo> infos = mActivityManager.getAllRootTaskInfos(); |
| for (RootTaskInfo info : infos) { |
| if (!info.visible) { |
| continue; |
| } |
| 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); |
| } |
| }); |
| } |
| } |