/* * 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> 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 onProcessStarted(int pid, int processUid, int packageUid, String packageName, String processName) { } @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 infos = mActivityManager.getAllRootTaskInfos(); for (RootTaskInfo info : infos) { if (!info.visible) { continue; } Set 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); } }); } }