1 /* 2 * Copyright (C) 2018 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 android.car.cluster; 17 18 import android.annotation.Nullable; 19 import android.annotation.UiThread; 20 import android.app.ActivityManager; 21 import android.app.ActivityTaskManager.RootTaskInfo; 22 import android.app.IActivityManager; 23 import android.app.IProcessObserver; 24 import android.app.TaskStackListener; 25 import android.content.ComponentName; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import java.util.HashMap; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Set; 36 37 /** 38 * Top activity monitor, allows listeners to be notified when a new activity comes to the foreground 39 * on a particular device. 40 * 41 * As a quick check {@link #notifyTopActivities} is handed to the UI thread because it is triggered 42 * by {@link #mProcessObserver} and {@link #mTaskStackListener}, which may be called by background 43 * threads. 44 * 45 * {@link #start} and {@link #stop} should be called only by the UI thread to prevent possible NPEs. 46 */ 47 public class ActivityMonitor { 48 private static final String TAG = "Cluster.ActivityMonitor"; 49 50 /** 51 * Listener of activity changes 52 */ 53 public interface ActivityListener { 54 /** 55 * Invoked when a new activity becomes the top activity on a particular display. 56 */ onTopActivityChanged(int displayId, @Nullable ComponentName activity)57 void onTopActivityChanged(int displayId, @Nullable ComponentName activity); 58 } 59 60 private IActivityManager mActivityManager; 61 // Listeners of top activity changes, indexed by the displayId they are interested on. 62 private final Map<Integer, Set<ActivityListener>> mListeners = new HashMap<>(); 63 private final Handler mHandler = new Handler(); 64 private final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() { 65 /** 66 * Note: This function may sometimes be called from a background thread 67 */ 68 @Override 69 public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { 70 notifyTopActivities(); 71 } 72 73 /** 74 * Note: This function may sometimes be called from a background thread 75 */ 76 @Override 77 public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) { } 78 79 @Override 80 public void onProcessStarted(int pid, int processUid, int packageUid, String packageName, 81 String processName) { } 82 83 @Override 84 public void onProcessDied(int pid, int uid) { 85 notifyTopActivities(); 86 } 87 }; 88 private final TaskStackListener mTaskStackListener = new TaskStackListener() { 89 /** 90 * Note: This function may sometimes be called from a background thread 91 */ 92 @Override 93 public void onTaskStackChanged() { 94 Log.i(TAG, "onTaskStackChanged"); 95 notifyTopActivities(); 96 } 97 }; 98 99 /** 100 * Registers a new listener to receive activity updates on a particular display 101 * 102 * @param displayId identifier of the display to monitor 103 * @param listener listener to be notified 104 */ addListener(int displayId, ActivityListener listener)105 public void addListener(int displayId, ActivityListener listener) { 106 mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).add(listener); 107 } 108 109 /** 110 * Unregisters a listener previously registered with {@link #addListener(int, ActivityListener)} 111 */ removeListener(int displayId, ActivityListener listener)112 public void removeListener(int displayId, ActivityListener listener) { 113 mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).remove(listener); 114 } 115 116 /** 117 * Starts monitoring activity changes. {@link #stop()} should be invoked to release resources. 118 * 119 * This method should be called on the UI thread. Otherwise, runtime exceptions may occur. 120 */ 121 @UiThread start()122 public void start() { 123 mActivityManager = ActivityManager.getService(); 124 // Monitoring both listeners are necessary as there are cases where one listener cannot 125 // monitor activity change. 126 try { 127 mActivityManager.registerProcessObserver(mProcessObserver); 128 mActivityManager.registerTaskStackListener(mTaskStackListener); 129 } catch (RemoteException e) { 130 Log.e(TAG, "Cannot register activity monitoring", e); 131 throw new RuntimeException(e); 132 } 133 notifyTopActivities(); 134 } 135 136 /** 137 * Stops monitoring activity changes. Should be invoked when this monitor is not longer used. 138 * 139 * This method should be called on the UI thread. Otherwise, runtime exceptions may occur. 140 */ 141 @UiThread stop()142 public void stop() { 143 if (mActivityManager == null) { 144 return; 145 } 146 if (Looper.getMainLooper().getThread() != Thread.currentThread()) { 147 Log.w(TAG, "stop() is called on non-UI thread. May cause NPE"); 148 } 149 try { 150 mActivityManager.unregisterProcessObserver(mProcessObserver); 151 mActivityManager.unregisterTaskStackListener(mTaskStackListener); 152 } catch (RemoteException e) { 153 Log.e(TAG, "Cannot unregister activity monitoring. Ignoring", e); 154 } 155 mActivityManager = null; 156 } 157 158 /** 159 * Notifies listeners on changes of top activities. 160 * 161 * Note: This method may sometimes be called by background threads, so it is synchronized on 162 * the UI thread with mHandler.post() 163 */ notifyTopActivities()164 private void notifyTopActivities() { 165 mHandler.post(() -> { 166 try { 167 // return if the activity monitor is no longer used 168 if (mActivityManager == null) { 169 return; 170 } 171 List<RootTaskInfo> infos = mActivityManager.getAllRootTaskInfos(); 172 for (RootTaskInfo info : infos) { 173 if (!info.visible) { 174 continue; 175 } 176 Set<ActivityListener> listeners = mListeners.get(info.displayId); 177 if (listeners != null && !listeners.isEmpty()) { 178 for (ActivityListener listener : listeners) { 179 listener.onTopActivityChanged(info.displayId, info.topActivity); 180 } 181 } 182 } 183 } catch (RemoteException e) { 184 Log.e(TAG, "Cannot getTasks", e); 185 } 186 }); 187 } 188 } 189