1 /*
2  * Copyright (C) 2016 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 com.android.car;
17 
18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
19 
20 import android.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.car.builtin.app.ActivityManagerHelper;
23 import android.car.builtin.app.ActivityManagerHelper.ProcessObserverCallback;
24 import android.car.builtin.os.ProcessHelper;
25 import android.car.builtin.os.UserManagerHelper;
26 import android.car.builtin.util.Slogf;
27 import android.content.Context;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Process;
33 import android.util.ArrayMap;
34 import android.util.ArraySet;
35 import android.util.Log;
36 import android.util.proto.ProtoOutputStream;
37 
38 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
39 import com.android.car.internal.util.IndentingPrintWriter;
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.lang.ref.WeakReference;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Set;
49 
50 /**
51  * Service to monitor AMS for new Activity or Service launching.
52  */
53 public class SystemActivityMonitoringService implements CarServiceBase {
54 
55     /** Injector for injecting some system related operations. */
56     @VisibleForTesting
57     /* package */ interface Injector {
registerProcessObserverCallback(ProcessObserverCallback callback)58         void registerProcessObserverCallback(ProcessObserverCallback callback);
unregisterProcessObserverCallback(ProcessObserverCallback callback)59         void unregisterProcessObserverCallback(ProcessObserverCallback callback);
getPassengerActivitySetProcessGroupRetryTimeoutMs()60         long getPassengerActivitySetProcessGroupRetryTimeoutMs();
61     }
62 
63     private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG);
64     // Passenger Activity might not be in top-app group in the 1st try. In that case, try
65     // again after this time. Retry will happen only once.
66     private static final long PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS = 200;
67 
68     private final ProcessObserverCallback mProcessObserver = new ProcessObserver();
69 
70     private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread(
71             getClass().getSimpleName());
72     private final ActivityMonitorHandler mHandler = new ActivityMonitorHandler(
73             mMonitorHandlerThread.getLooper(), this);
74 
75     private final Context mContext;
76 
77     private final Injector mInjector;
78 
79     private final Object mLock = new Object();
80 
81     @GuardedBy("mLock")
82     private final Map<Integer, Set<Integer>> mForegroundUidPids = new ArrayMap<>();
83 
84     @GuardedBy("mLock")
85     private final List<ProcessObserverCallback> mCustomProcessObservers = new ArrayList<>();
86 
87     @GuardedBy("mLock")
88     private boolean mAssignPassengerActivityToFgGroup;
89 
90     @GuardedBy("mLock")
91     private int mDriverTopAppPid = Process.INVALID_PID;
92 
SystemActivityMonitoringService(Context context)93     public SystemActivityMonitoringService(Context context) {
94         this(context, new DefaultInjector());
95     }
96 
97     @VisibleForTesting
SystemActivityMonitoringService(Context context, Injector injector)98     /*package*/ SystemActivityMonitoringService(Context context, Injector injector) {
99         mContext = context;
100         mInjector = injector;
101     }
102 
103     @Override
init()104     public void init() {
105         boolean assignPassengerActivityToFgGroup = false;
106         if (mContext.getResources().getBoolean(
107                 R.bool.config_assignPassengerActivityToForegroundCpuGroup)) {
108             CarOccupantZoneService occupantService = CarLocalServices.getService(
109                     CarOccupantZoneService.class);
110             if (occupantService.hasDriverZone() && occupantService.hasPassengerZones()) {
111                 assignPassengerActivityToFgGroup = true;
112             }
113         }
114         synchronized (mLock) {
115             mAssignPassengerActivityToFgGroup = assignPassengerActivityToFgGroup;
116         }
117         // Monitoring both listeners are necessary as there are cases where one listener cannot
118         // monitor activity change.
119         mInjector.registerProcessObserverCallback(mProcessObserver);
120     }
121 
122     @Override
release()123     public void release() {
124         mInjector.unregisterProcessObserverCallback(mProcessObserver);
125     }
126 
127     @Override
128     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)129     public void dump(IndentingPrintWriter writer) {
130         writer.println("*SystemActivityMonitoringService*");
131         writer.println(" Top Tasks per display:");
132         synchronized (mLock) {
133             writer.println(" Foreground uid-pids:");
134             for (Integer key : mForegroundUidPids.keySet()) {
135                 Set<Integer> pids = mForegroundUidPids.get(key);
136                 if (pids == null) {
137                     continue;
138                 }
139                 writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray()));
140             }
141             writer.println(
142                     "mAssignPassengerActivityToFgGroup:" + mAssignPassengerActivityToFgGroup);
143         }
144     }
145 
146     @Override
147     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)148     public void dumpProto(ProtoOutputStream proto) {}
149 
150     /**
151      * Returns {@code true} if given pid-uid pair is in foreground.
152      */
isInForeground(int pid, int uid)153     public boolean isInForeground(int pid, int uid) {
154         Set<Integer> pids = getPidsOfForegroudApp(uid);
155         if (pids == null) {
156             return false;
157         }
158         return pids.contains(pid);
159     }
160 
161     /**
162      * Returns PIDs of foreground apps launched from the given UID.
163      */
164     @Nullable
getPidsOfForegroudApp(int uid)165     public Set<Integer> getPidsOfForegroudApp(int uid) {
166         synchronized (mLock) {
167             return mForegroundUidPids.get(uid);
168         }
169     }
170 
171     /** Registers a callback to get notified when the running state of a process has changed. */
registerProcessObserverCallback(ProcessObserverCallback callback)172     public void registerProcessObserverCallback(ProcessObserverCallback callback) {
173         synchronized (mLock) {
174             mCustomProcessObservers.add(callback);
175         }
176     }
177 
178     /** Unregisters the ProcessObserverCallback, if there is any. */
unregisterProcessObserverCallback(ProcessObserverCallback callback)179     public void unregisterProcessObserverCallback(ProcessObserverCallback callback) {
180         synchronized (mLock) {
181             mCustomProcessObservers.remove(callback);
182         }
183     }
184 
handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)185     private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
186         synchronized (mLock) {
187             for (int i = 0; i < mCustomProcessObservers.size(); i++) {
188                 ProcessObserverCallback callback = mCustomProcessObservers.get(i);
189                 callback.onForegroundActivitiesChanged(pid, uid, foregroundActivities);
190             }
191             if (foregroundActivities) {
192                 Set<Integer> pids = mForegroundUidPids.get(uid);
193                 if (pids == null) {
194                     pids = new ArraySet<Integer>();
195                     mForegroundUidPids.put(uid, pids);
196                 }
197                 pids.add(pid);
198             } else {
199                 doHandlePidGoneLocked(pid, uid);
200             }
201         }
202     }
203 
handleProcessDied(int pid, int uid)204     private void handleProcessDied(int pid, int uid) {
205         synchronized (mLock) {
206             for (int i = 0; i < mCustomProcessObservers.size(); i++) {
207                 ProcessObserverCallback callback = mCustomProcessObservers.get(i);
208                 callback.onProcessDied(pid, uid);
209             }
210             doHandlePidGoneLocked(pid, uid);
211         }
212     }
213 
214     @GuardedBy("mLock")
doHandlePidGoneLocked(int pid, int uid)215     private void doHandlePidGoneLocked(int pid, int uid) {
216         Set<Integer> pids = mForegroundUidPids.get(uid);
217         if (pids != null) {
218             pids.remove(pid);
219             if (pids.isEmpty()) {
220                 mForegroundUidPids.remove(uid);
221             }
222         }
223     }
224 
225     /**
226      * Updates the process group for given PID if it is passenger app and returns true if it should
227      * be retried.
228      */
updateProcessGroupForFgApp(int pid, int uid)229     private boolean updateProcessGroupForFgApp(int pid, int uid) {
230         int driverTopAppPid;
231         synchronized (mLock) {
232             if (!mAssignPassengerActivityToFgGroup) {
233                 return false;
234             }
235             int userId = UserManagerHelper.getUserId(uid);
236             // Current user will be driver. So do not touch it.
237             // User 0 will be either current user or common system UI which should run with higher
238             // priority.
239             if (userId == ActivityManager.getCurrentUser()
240                     || userId == UserManagerHelper.USER_SYSTEM) {
241                 mDriverTopAppPid = pid;
242                 return false;
243             }
244             driverTopAppPid = mDriverTopAppPid;
245         }
246         // TODO(b/261783537) ignore profile of the current user
247 
248         CarServiceHelperWrapper helper = CarServiceHelperWrapper.getInstance();
249         boolean shouldRetry = false;
250         try {
251             int processGroup = helper.getProcessGroup(pid);
252             if (DBG) {
253                 Slogf.d(CarLog.TAG_AM, "doHandleProcessGroupForFgApp: pid=%d pGroup=%d",
254                         pid, processGroup);
255             }
256             switch (processGroup) {
257                 case ProcessHelper.THREAD_GROUP_FOREGROUND:
258                     // SetProcessGroup happens in OomAdjuster#mProcessGroupHandler in System
259                     // Server, but which is the different thread with the main thread of
260                     // OomAdjuster, and the focus change event is propagated to CarService
261                     // through Binder, so there is race-condition between setting ProcessGroup in
262                     // System Server and here.
263                     // So, there are chances that to set Top App is not executed yet.
264                     shouldRetry = true;
265                     break;
266                 case ProcessHelper.THREAD_GROUP_TOP_APP:
267                     // Changing to FOREGROUND requires setting it to DEFAULT
268                     helper.setProcessGroup(pid, ProcessHelper.THREAD_GROUP_DEFAULT);
269                     if (driverTopAppPid != Process.INVALID_PID) {
270                         helper.setProcessGroup(driverTopAppPid, ProcessHelper.THREAD_GROUP_TOP_APP);
271                     }
272                     break;
273                 default:
274                     // not in top-app yet, should retry
275                     shouldRetry = true;
276                     break;
277             }
278         } catch (Exception e) {
279             Slogf.w(CarLog.TAG_AM, e, "Process group manipulation failed for pid:%d uid:%d",
280                     pid, uid);
281             // no need to retry as this PID may be already invalid.
282         }
283         return shouldRetry;
284     }
285 
handleProcessGroupForFgApp(int pid, int uid)286     private void handleProcessGroupForFgApp(int pid, int uid) {
287         if (updateProcessGroupForFgApp(pid, uid)) {
288             if (DBG) {
289                 Slogf.d(CarLog.TAG_AM, "Will retry handleProcessGroupForFgApp: pid=%d, uid=%d",
290                         pid, uid);
291             }
292             mHandler.postDelayed(() -> updateProcessGroupForFgApp(pid, uid),
293                     mInjector.getPassengerActivitySetProcessGroupRetryTimeoutMs());
294         }
295     }
296 
handleProcessGroupForDiedApp(int pid)297     private void handleProcessGroupForDiedApp(int pid) {
298         synchronized (mLock) {
299             if (!mAssignPassengerActivityToFgGroup) {
300                 return;
301             }
302             if (pid == mDriverTopAppPid) {
303                 mDriverTopAppPid = Process.INVALID_PID;
304             }
305         }
306     }
307 
308     /** Handles focusChanged event. */
handleFocusChanged(int pid, int uid)309     public void handleFocusChanged(int pid, int uid) {
310         if (DBG) Slogf.d(CarLog.TAG_AM, "notifyFocusChanged: pid=%d uid=%d", pid, uid);
311         handleProcessGroupForFgApp(pid, uid);
312     }
313 
314     private class ProcessObserver extends ProcessObserverCallback {
315         @Override
onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)316         public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
317             if (Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
318                 Slogf.d(CarLog.TAG_AM,
319                         String.format("onForegroundActivitiesChanged uid %d pid %d fg %b",
320                                 uid, pid, foregroundActivities));
321             }
322             mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities);
323         }
324 
325         @Override
onProcessDied(int pid, int uid)326         public void onProcessDied(int pid, int uid) {
327             handleProcessGroupForDiedApp(pid);
328             mHandler.requestProcessDied(pid, uid);
329         }
330     }
331 
332     private static final class ActivityMonitorHandler extends Handler {
333         private static final String TAG = ActivityMonitorHandler.class.getSimpleName();
334 
335         private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1;
336         private static final int MSG_PROCESS_DIED = 2;
337 
338         private final WeakReference<SystemActivityMonitoringService> mService;
339 
ActivityMonitorHandler(Looper looper, SystemActivityMonitoringService service)340         private ActivityMonitorHandler(Looper looper, SystemActivityMonitoringService service) {
341             super(looper);
342             mService = new WeakReference<SystemActivityMonitoringService>(service);
343         }
344 
requestForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities)345         private void requestForegroundActivitiesChanged(int pid, int uid,
346                 boolean foregroundActivities) {
347             Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid,
348                     Boolean.valueOf(foregroundActivities));
349             sendMessage(msg);
350         }
351 
requestProcessDied(int pid, int uid)352         private void requestProcessDied(int pid, int uid) {
353             Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid);
354             sendMessage(msg);
355         }
356 
357         @Override
handleMessage(Message msg)358         public void handleMessage(Message msg) {
359             SystemActivityMonitoringService service = mService.get();
360             if (service == null) {
361                 Slogf.i(TAG, "handleMessage null service");
362                 return;
363             }
364             switch (msg.what) {
365                 case MSG_FOREGROUND_ACTIVITIES_CHANGED:
366                     service.handleForegroundActivitiesChanged(msg.arg1, msg.arg2,
367                             (Boolean) msg.obj);
368                     break;
369                 case MSG_PROCESS_DIED:
370                     service.handleProcessDied(msg.arg1, msg.arg2);
371                     break;
372                 default:
373                     break;
374             }
375         }
376     }
377 
378     private static class DefaultInjector implements Injector {
379         @Override
registerProcessObserverCallback(ProcessObserverCallback callback)380         public void registerProcessObserverCallback(ProcessObserverCallback callback) {
381             ActivityManagerHelper.registerProcessObserverCallback(callback);
382         }
383 
384         @Override
unregisterProcessObserverCallback(ProcessObserverCallback callback)385         public void unregisterProcessObserverCallback(ProcessObserverCallback callback) {
386             ActivityManagerHelper.unregisterProcessObserverCallback(callback);
387         }
388 
389         @Override
getPassengerActivitySetProcessGroupRetryTimeoutMs()390         public long getPassengerActivitySetProcessGroupRetryTimeoutMs() {
391             return PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS;
392         }
393     }
394 }
395