From 59ee488c495f35994e816c57e4d37c19e8359cc7 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Wed, 3 Nov 2021 12:15:13 -0700 Subject: [PATCH] Infra for SysUI to monitor FGSes There's now a way for it to track what apps currently have services in the FGS state in real time. Bug: 192504071 Test: manual Test: atest CtsAppTestCases:android.app.cts.ServiceTest Ignore-AOSP-First: new API should not be revealed in AOSP prior to drop Change-Id: Ief1b6f8ef7deba6abbbf42f20b3f498861c1fe69 --- core/java/android/app/IActivityManager.aidl | 3 + .../app/IForegroundServiceObserver.aidl | 31 ++++++++ core/java/android/app/OWNERS | 1 + .../com/android/server/am/ActiveServices.java | 71 ++++++++++++++++++- .../server/am/ActivityManagerService.java | 27 ++++++- 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 core/java/android/app/IForegroundServiceObserver.aidl diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 658c1309b94e6..6b4319ac06e35 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -26,6 +26,7 @@ import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityController; import android.app.IAppTask; +import android.app.IForegroundServiceObserver; import android.app.IInstrumentationWatcher; import android.app.IProcessObserver; import android.app.IServiceConnection; @@ -279,6 +280,8 @@ interface IActivityManager { boolean clearApplicationUserData(in String packageName, boolean keepState, in IPackageDataObserver observer, int userId); void makeServicesNonForeground(in String packageName, int userId); + /** Returns {@code false} if the callback could not be registered, {@true} otherwise. */ + boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback); @UnsupportedAppUsage void forceStopPackage(in String packageName, int userId); boolean killPids(in int[] pids, in String reason, boolean secure); diff --git a/core/java/android/app/IForegroundServiceObserver.aidl b/core/java/android/app/IForegroundServiceObserver.aidl new file mode 100644 index 0000000000000..2b0cbed8a5078 --- /dev/null +++ b/core/java/android/app/IForegroundServiceObserver.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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 android.app; + +/** + * Notify the client of all changes to services' foreground state. + * @param serviceToken unique identifier for a service instance + * @param packageName identifies the app hosting the service + * @param userId identifies the started user in which the app is running + * @param isForeground whether the service is in the "foreground" mode now, i.e. + * whether it is an FGS + * + * @hide + */ +oneway interface IForegroundServiceObserver { + void onForegroundStateChanged(in IBinder serviceToken, in String packageName, int userId, boolean isForeground); +} diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 4da51c13045b2..9da5693ff4804 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -15,6 +15,7 @@ per-file IActivityController.aidl = file:/services/core/java/com/android/server/ per-file IActivityManager.aidl = file:/services/core/java/com/android/server/am/OWNERS per-file IApplicationThread.aidl = file:/services/core/java/com/android/server/am/OWNERS per-file IAppTraceRetriever.aidl = file:/services/core/java/com/android/server/am/OWNERS +per-file IForegroundServiceObserver.aidl = file:/services/core/java/com/android/server/am/OWNERS per-file IInstrumentationWatcher.aidl = file:/services/core/java/com/android/server/am/OWNERS per-file IntentService.aidl = file:/services/core/java/com/android/server/am/OWNERS per-file IServiceConnection.aidl = file:/services/core/java/com/android/server/am/OWNERS diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index b5623be0e731e..bf744cff779ed 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -90,6 +90,7 @@ import android.app.AppOpsManager; import android.app.ForegroundServiceDidNotStartInTimeException; import android.app.ForegroundServiceStartNotAllowedException; import android.app.IApplicationThread; +import android.app.IForegroundServiceObserver; import android.app.IServiceConnection; import android.app.Notification; import android.app.NotificationManager; @@ -128,6 +129,7 @@ import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; import android.os.Process; import android.os.RemoteCallback; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -266,6 +268,12 @@ public final class ActiveServices { */ final SparseLongArray mFgsDeferralEligible = new SparseLongArray(); + /** + * Foreground service observers: track what apps have FGSes + */ + final RemoteCallbackList mFgsObservers = + new RemoteCallbackList<>(); + /** * Map of services that are asked to be brought up (start/binding) but not ready to. */ @@ -1382,6 +1390,10 @@ public final class ActiveServices { return false; } + /** + * Put the named service into the foreground mode + */ + @GuardedBy("mAm") public void setServiceForegroundLocked(ComponentName className, IBinder token, int id, Notification notification, int flags, int foregroundServiceType) { final int userId = UserHandle.getCallingUserId(); @@ -1746,6 +1758,7 @@ public final class ActiveServices { /** * @param id Notification ID. Zero === exit foreground state for the given service. */ + @GuardedBy("mAm") private void setServiceForegroundInnerLocked(final ServiceRecord r, int id, Notification notification, int flags, int foregroundServiceType) { if (id != 0) { @@ -1981,6 +1994,7 @@ public final class ActiveServices { } // Even if the service is already a FGS, we need to update the notification, // so we need to call it again. + signalForegroundServiceObserversLocked(r); r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(psr, true); @@ -2060,6 +2074,7 @@ public final class ActiveServices { r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); r.mFgsNotificationWasDeferred = false; + signalForegroundServiceObserversLocked(r); resetFgsRestrictionLocked(r); mAm.updateForegroundServiceUsageStats(r.name, r.userId, false); if (r.app != null) { @@ -4552,7 +4567,8 @@ public final class ActiveServices { } cancelForegroundNotificationLocked(r); - if (r.isForeground) { + final boolean exitingFg = r.isForeground; + if (exitingFg) { decActiveForegroundAppLocked(smap, r); synchronized (mAm.mProcessStats.mLock) { ServiceState stracker = r.getTracker(); @@ -4578,6 +4594,11 @@ public final class ActiveServices { r.foregroundId = 0; r.foregroundNoti = null; resetFgsRestrictionLocked(r); + // Signal FGS observers *after* changing the isForeground state, and + // only if this was an actual state change. + if (exitingFg) { + signalForegroundServiceObserversLocked(r); + } // Clear start entries. r.clearDeliveredStartsLocked(); @@ -5133,6 +5154,54 @@ public final class ActiveServices { } } + @GuardedBy("mAm") + private void signalForegroundServiceObserversLocked(ServiceRecord r) { + final int num = mFgsObservers.beginBroadcast(); + for (int i = 0; i < num; i++) { + try { + mFgsObservers.getBroadcastItem(i).onForegroundStateChanged(r, + r.appInfo.packageName, r.userId, r.isForeground); + } catch (RemoteException e) { + // Will be unregistered automatically by RemoteCallbackList's dead-object + // tracking, so nothing we need to do here. + } + } + mFgsObservers.finishBroadcast(); + } + + @GuardedBy("mAm") + boolean registerForegroundServiceObserverLocked(final int callingUid, + IForegroundServiceObserver callback) { + // We always tell the newly-registered observer about any current FGSes. The + // most common case for this is a SysUI crash & relaunch; it needs to + // reconstruct its tracking of stoppable-FGS-hosting apps. + try { + final int mapSize = mServiceMap.size(); + for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) { + final ServiceMap smap = mServiceMap.valueAt(mapIndex); + if (smap != null) { + final int numServices = smap.mServicesByInstanceName.size(); + for (int i = 0; i < numServices; i++) { + final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); + if (sr.isForeground && callingUid == sr.appInfo.uid) { + callback.onForegroundStateChanged(sr, sr.appInfo.packageName, + sr.userId, true); + } + } + } + } + // Callback is fine, go ahead and record it + mFgsObservers.register(callback); + } catch (RemoteException e) { + // Whoops, something wrong with the callback. Don't register it, and + // report error back to the caller. + Slog.e(TAG_SERVICE, "Bad FGS observer from uid " + callingUid); + return false; + } + + return true; + } + void forceStopPackageLocked(String packageName, int userId) { ServiceMap smap = mServiceMap.get(userId); if (smap != null && smap.mActiveForegroundApps.size() > 0) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index cf4d0488afb01..c27323dab4b2c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -21,6 +21,7 @@ import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST; import static android.Manifest.permission.FILTER_EVENTS; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND; import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS; @@ -167,6 +168,7 @@ import android.app.ContentProviderHolder; import android.app.IActivityController; import android.app.IActivityManager; import android.app.IApplicationThread; +import android.app.IForegroundServiceObserver; import android.app.IInstrumentationWatcher; import android.app.INotificationManager; import android.app.IProcessObserver; @@ -3753,12 +3755,12 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void makeServicesNonForeground(final String packageName, int userId) { - if (checkCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) + if (checkCallingPermission(MANAGE_ACTIVITY_TASKS) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: makeServicesNonForeground() from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " requires " + android.Manifest.permission.MANAGE_ACTIVITY_TASKS; + + " requires " + MANAGE_ACTIVITY_TASKS; Slog.w(TAG, msg); throw new SecurityException(msg); } @@ -3774,6 +3776,27 @@ public class ActivityManagerService extends IActivityManager.Stub } } + @Override + public boolean registerForegroundServiceObserver(IForegroundServiceObserver callback) { + final int callingUid = Binder.getCallingUid(); + final int permActivityTasks = checkCallingPermission(MANAGE_ACTIVITY_TASKS); + final int permAcrossUsersFull = checkCallingPermission(INTERACT_ACROSS_USERS_FULL); + if (permActivityTasks != PackageManager.PERMISSION_GRANTED + || permAcrossUsersFull != PERMISSION_GRANTED) { + String msg = "Permission Denial: registerForegroundServiceObserver() from pid=" + + Binder.getCallingPid() + + ", uid=" + callingUid + + " requires " + MANAGE_ACTIVITY_TASKS + + " and " + INTERACT_ACROSS_USERS_FULL; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + synchronized (this) { + return mServices.registerForegroundServiceObserverLocked(callingUid, callback); + } + } + @Override public void forceStopPackage(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)