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