/* * Copyright (C) 2016 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 com.android.car; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.Nullable; import android.app.ActivityManager; import android.car.builtin.app.ActivityManagerHelper; import android.car.builtin.app.ActivityManagerHelper.ProcessObserverCallback; import android.car.builtin.os.ProcessHelper; import android.car.builtin.os.UserManagerHelper; import android.car.builtin.util.Slogf; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.car.internal.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; /** * Service to monitor AMS for new Activity or Service launching. */ public class SystemActivityMonitoringService implements CarServiceBase { /** Injector for injecting some system related operations. */ @VisibleForTesting /* package */ interface Injector { void registerProcessObserverCallback(ProcessObserverCallback callback); void unregisterProcessObserverCallback(ProcessObserverCallback callback); long getPassengerActivitySetProcessGroupRetryTimeoutMs(); } private static final boolean DBG = Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG); // Passenger Activity might not be in top-app group in the 1st try. In that case, try // again after this time. Retry will happen only once. private static final long PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS = 200; private final ProcessObserverCallback mProcessObserver = new ProcessObserver(); private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread( getClass().getSimpleName()); private final ActivityMonitorHandler mHandler = new ActivityMonitorHandler( mMonitorHandlerThread.getLooper(), this); private final Context mContext; private final Injector mInjector; private final Object mLock = new Object(); @GuardedBy("mLock") private final Map> mForegroundUidPids = new ArrayMap<>(); @GuardedBy("mLock") private final List mCustomProcessObservers = new ArrayList<>(); @GuardedBy("mLock") private boolean mAssignPassengerActivityToFgGroup; @GuardedBy("mLock") private int mDriverTopAppPid = Process.INVALID_PID; public SystemActivityMonitoringService(Context context) { this(context, new DefaultInjector()); } @VisibleForTesting /*package*/ SystemActivityMonitoringService(Context context, Injector injector) { mContext = context; mInjector = injector; } @Override public void init() { boolean assignPassengerActivityToFgGroup = false; if (mContext.getResources().getBoolean( R.bool.config_assignPassengerActivityToForegroundCpuGroup)) { CarOccupantZoneService occupantService = CarLocalServices.getService( CarOccupantZoneService.class); if (occupantService.hasDriverZone() && occupantService.hasPassengerZones()) { assignPassengerActivityToFgGroup = true; } } synchronized (mLock) { mAssignPassengerActivityToFgGroup = assignPassengerActivityToFgGroup; } // Monitoring both listeners are necessary as there are cases where one listener cannot // monitor activity change. mInjector.registerProcessObserverCallback(mProcessObserver); } @Override public void release() { mInjector.unregisterProcessObserverCallback(mProcessObserver); } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dump(IndentingPrintWriter writer) { writer.println("*SystemActivityMonitoringService*"); writer.println(" Top Tasks per display:"); synchronized (mLock) { writer.println(" Foreground uid-pids:"); for (Integer key : mForegroundUidPids.keySet()) { Set pids = mForegroundUidPids.get(key); if (pids == null) { continue; } writer.println("uid:" + key + ", pids:" + Arrays.toString(pids.toArray())); } writer.println( "mAssignPassengerActivityToFgGroup:" + mAssignPassengerActivityToFgGroup); } } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) public void dumpProto(ProtoOutputStream proto) {} /** * Returns {@code true} if given pid-uid pair is in foreground. */ public boolean isInForeground(int pid, int uid) { Set pids = getPidsOfForegroudApp(uid); if (pids == null) { return false; } return pids.contains(pid); } /** * Returns PIDs of foreground apps launched from the given UID. */ @Nullable public Set getPidsOfForegroudApp(int uid) { synchronized (mLock) { return mForegroundUidPids.get(uid); } } /** Registers a callback to get notified when the running state of a process has changed. */ public void registerProcessObserverCallback(ProcessObserverCallback callback) { synchronized (mLock) { mCustomProcessObservers.add(callback); } } /** Unregisters the ProcessObserverCallback, if there is any. */ public void unregisterProcessObserverCallback(ProcessObserverCallback callback) { synchronized (mLock) { mCustomProcessObservers.remove(callback); } } private void handleForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { synchronized (mLock) { for (int i = 0; i < mCustomProcessObservers.size(); i++) { ProcessObserverCallback callback = mCustomProcessObservers.get(i); callback.onForegroundActivitiesChanged(pid, uid, foregroundActivities); } if (foregroundActivities) { Set pids = mForegroundUidPids.get(uid); if (pids == null) { pids = new ArraySet(); mForegroundUidPids.put(uid, pids); } pids.add(pid); } else { doHandlePidGoneLocked(pid, uid); } } } private void handleProcessDied(int pid, int uid) { synchronized (mLock) { for (int i = 0; i < mCustomProcessObservers.size(); i++) { ProcessObserverCallback callback = mCustomProcessObservers.get(i); callback.onProcessDied(pid, uid); } doHandlePidGoneLocked(pid, uid); } } @GuardedBy("mLock") private void doHandlePidGoneLocked(int pid, int uid) { Set pids = mForegroundUidPids.get(uid); if (pids != null) { pids.remove(pid); if (pids.isEmpty()) { mForegroundUidPids.remove(uid); } } } /** * Updates the process group for given PID if it is passenger app and returns true if it should * be retried. */ private boolean updateProcessGroupForFgApp(int pid, int uid) { int driverTopAppPid; synchronized (mLock) { if (!mAssignPassengerActivityToFgGroup) { return false; } int userId = UserManagerHelper.getUserId(uid); // Current user will be driver. So do not touch it. // User 0 will be either current user or common system UI which should run with higher // priority. if (userId == ActivityManager.getCurrentUser() || userId == UserManagerHelper.USER_SYSTEM) { mDriverTopAppPid = pid; return false; } driverTopAppPid = mDriverTopAppPid; } // TODO(b/261783537) ignore profile of the current user CarServiceHelperWrapper helper = CarServiceHelperWrapper.getInstance(); boolean shouldRetry = false; try { int processGroup = helper.getProcessGroup(pid); if (DBG) { Slogf.d(CarLog.TAG_AM, "doHandleProcessGroupForFgApp: pid=%d pGroup=%d", pid, processGroup); } switch (processGroup) { case ProcessHelper.THREAD_GROUP_FOREGROUND: // SetProcessGroup happens in OomAdjuster#mProcessGroupHandler in System // Server, but which is the different thread with the main thread of // OomAdjuster, and the focus change event is propagated to CarService // through Binder, so there is race-condition between setting ProcessGroup in // System Server and here. // So, there are chances that to set Top App is not executed yet. shouldRetry = true; break; case ProcessHelper.THREAD_GROUP_TOP_APP: // Changing to FOREGROUND requires setting it to DEFAULT helper.setProcessGroup(pid, ProcessHelper.THREAD_GROUP_DEFAULT); if (driverTopAppPid != Process.INVALID_PID) { helper.setProcessGroup(driverTopAppPid, ProcessHelper.THREAD_GROUP_TOP_APP); } break; default: // not in top-app yet, should retry shouldRetry = true; break; } } catch (Exception e) { Slogf.w(CarLog.TAG_AM, e, "Process group manipulation failed for pid:%d uid:%d", pid, uid); // no need to retry as this PID may be already invalid. } return shouldRetry; } private void handleProcessGroupForFgApp(int pid, int uid) { if (updateProcessGroupForFgApp(pid, uid)) { if (DBG) { Slogf.d(CarLog.TAG_AM, "Will retry handleProcessGroupForFgApp: pid=%d, uid=%d", pid, uid); } mHandler.postDelayed(() -> updateProcessGroupForFgApp(pid, uid), mInjector.getPassengerActivitySetProcessGroupRetryTimeoutMs()); } } private void handleProcessGroupForDiedApp(int pid) { synchronized (mLock) { if (!mAssignPassengerActivityToFgGroup) { return; } if (pid == mDriverTopAppPid) { mDriverTopAppPid = Process.INVALID_PID; } } } /** Handles focusChanged event. */ public void handleFocusChanged(int pid, int uid) { if (DBG) Slogf.d(CarLog.TAG_AM, "notifyFocusChanged: pid=%d uid=%d", pid, uid); handleProcessGroupForFgApp(pid, uid); } private class ProcessObserver extends ProcessObserverCallback { @Override public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { if (Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG)) { Slogf.d(CarLog.TAG_AM, String.format("onForegroundActivitiesChanged uid %d pid %d fg %b", uid, pid, foregroundActivities)); } mHandler.requestForegroundActivitiesChanged(pid, uid, foregroundActivities); } @Override public void onProcessDied(int pid, int uid) { handleProcessGroupForDiedApp(pid); mHandler.requestProcessDied(pid, uid); } } private static final class ActivityMonitorHandler extends Handler { private static final String TAG = ActivityMonitorHandler.class.getSimpleName(); private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 1; private static final int MSG_PROCESS_DIED = 2; private final WeakReference mService; private ActivityMonitorHandler(Looper looper, SystemActivityMonitoringService service) { super(looper); mService = new WeakReference(service); } private void requestForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) { Message msg = obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED, pid, uid, Boolean.valueOf(foregroundActivities)); sendMessage(msg); } private void requestProcessDied(int pid, int uid) { Message msg = obtainMessage(MSG_PROCESS_DIED, pid, uid); sendMessage(msg); } @Override public void handleMessage(Message msg) { SystemActivityMonitoringService service = mService.get(); if (service == null) { Slogf.i(TAG, "handleMessage null service"); return; } switch (msg.what) { case MSG_FOREGROUND_ACTIVITIES_CHANGED: service.handleForegroundActivitiesChanged(msg.arg1, msg.arg2, (Boolean) msg.obj); break; case MSG_PROCESS_DIED: service.handleProcessDied(msg.arg1, msg.arg2); break; default: break; } } } private static class DefaultInjector implements Injector { @Override public void registerProcessObserverCallback(ProcessObserverCallback callback) { ActivityManagerHelper.registerProcessObserverCallback(callback); } @Override public void unregisterProcessObserverCallback(ProcessObserverCallback callback) { ActivityManagerHelper.unregisterProcessObserverCallback(callback); } @Override public long getPassengerActivitySetProcessGroupRetryTimeoutMs() { return PASSENGER_ACTIVITY_SET_PROCESS_GROUP_RETRY_MS; } } }