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.ActivityManager.StackInfo;
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 sanity 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 onProcessDied(int pid, int uid) {
81             notifyTopActivities();
82         }
83     };
84     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
85         /**
86          * Note: This function may sometimes be called from a background thread
87          */
88         @Override
89         public void onTaskStackChanged() {
90             Log.i(TAG, "onTaskStackChanged");
91             notifyTopActivities();
92         }
93     };
94 
95     /**
96      * Registers a new listener to receive activity updates on a particular display
97      *
98      * @param displayId identifier of the display to monitor
99      * @param listener  listener to be notified
100      */
addListener(int displayId, ActivityListener listener)101     public void addListener(int displayId, ActivityListener listener) {
102         mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).add(listener);
103     }
104 
105     /**
106      * Unregisters a listener previously registered with {@link #addListener(int, ActivityListener)}
107      */
removeListener(int displayId, ActivityListener listener)108     public void removeListener(int displayId, ActivityListener listener) {
109         mListeners.computeIfAbsent(displayId, k -> new HashSet<>()).remove(listener);
110     }
111 
112     /**
113      * Starts monitoring activity changes. {@link #stop()} should be invoked to release resources.
114      *
115      * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
116      */
117     @UiThread
start()118     public void start() {
119         mActivityManager = ActivityManager.getService();
120         // Monitoring both listeners are necessary as there are cases where one listener cannot
121         // monitor activity change.
122         try {
123             mActivityManager.registerProcessObserver(mProcessObserver);
124             mActivityManager.registerTaskStackListener(mTaskStackListener);
125         } catch (RemoteException e) {
126             Log.e(TAG, "Cannot register activity monitoring", e);
127             throw new RuntimeException(e);
128         }
129         notifyTopActivities();
130     }
131 
132     /**
133      * Stops monitoring activity changes. Should be invoked when this monitor is not longer used.
134      *
135      * This method should be called on the UI thread. Otherwise, runtime exceptions may occur.
136      */
137     @UiThread
stop()138     public void stop() {
139         if (mActivityManager == null) {
140             return;
141         }
142         if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
143             Log.w(TAG, "stop() is called on non-UI thread. May cause NPE");
144         }
145         try {
146             mActivityManager.unregisterProcessObserver(mProcessObserver);
147             mActivityManager.unregisterTaskStackListener(mTaskStackListener);
148         } catch (RemoteException e) {
149             Log.e(TAG, "Cannot unregister activity monitoring. Ignoring", e);
150         }
151         mActivityManager = null;
152     }
153 
154     /**
155      * Notifies listeners on changes of top activities.
156      *
157      * Note: This method may sometimes be called by background threads, so it is synchronized on
158      * the UI thread with mHandler.post()
159      */
notifyTopActivities()160     private void notifyTopActivities() {
161         mHandler.post(() -> {
162             try {
163                 // return if the activity monitor is no longer used
164                 if (mActivityManager == null) {
165                     return;
166                 }
167                 List<StackInfo> infos = mActivityManager.getAllStackInfos();
168                 for (StackInfo info : infos) {
169                     Set<ActivityListener> listeners = mListeners.get(info.displayId);
170                     if (listeners != null && !listeners.isEmpty()) {
171                         for (ActivityListener listener : listeners) {
172                             listener.onTopActivityChanged(info.displayId, info.topActivity);
173                         }
174                     }
175                 }
176             } catch (RemoteException e) {
177                 Log.e(TAG, "Cannot getTasks", e);
178             }
179         });
180     }
181 }
182