From c7933acad1e6a66f7858bc92b057f6fd6d72c803 Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Mon, 12 Mar 2018 17:57:09 -0700 Subject: [PATCH] Suppress all background-state services in user-forced app standby Foreground services are treated as normal services when the user has placed the app under FAS restrictions. The API contract for the app is unchanged -- for example, if the app calls startForegroundService() in this state it will still ANR if it fails to call startForeground() as promised. All service starts are quietly suppressed unless the app is showing foreground UI. If an app is already running a foreground service when FAS is applied, that service is taken out of the fg state and thereafter treated like an ordinary background service in a background state: after 1 minute, the service is summarily stopped by the OS. Bug: 73559697 Test: ApiDemos fg service suite Test: atest CtsAppTestCases Change-Id: Icc5fb180b90cb5d902e96aeb92a93f6daf00bd9b --- .../com/android/server/AppStateTracker.java | 23 +++ .../com/android/server/am/ActiveServices.java | 160 +++++++++++++----- .../server/am/ActivityManagerService.java | 11 +- .../com/android/server/am/BroadcastQueue.java | 2 +- 4 files changed, 148 insertions(+), 48 deletions(-) diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java index 16be680b17709..2affb74b485cf 100644 --- a/services/core/java/com/android/server/AppStateTracker.java +++ b/services/core/java/com/android/server/AppStateTracker.java @@ -266,6 +266,12 @@ public class AppStateTracker { // we need to deliver the allow-while-idle alarms for this uid, package unblockAllUnrestrictedAlarms(); } + + if (!sender.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)) { + Slog.v(TAG, "Package " + packageName + "/" + uid + + " toggled into fg service restriction"); + stopForegroundServicesForUidPackage(uid, packageName); + } } /** @@ -358,6 +364,13 @@ public class AppStateTracker { public void updateJobsForUidPackage(int uid, String packageName, boolean isNowActive) { } + /** + * Called when an app goes into forced app standby and its foreground + * services need to be removed from that state. + */ + public void stopForegroundServicesForUidPackage(int uid, String packageName) { + } + /** * Called when the job restrictions for multiple UIDs might have changed, so the alarm * manager should re-evaluate all restrictions for all blocked jobs. @@ -1061,6 +1074,16 @@ public class AppStateTracker { hasForegroundExemption); } + /** + * @return whether foreground services should be suppressed in the background + * due to forced app standby for the given app + */ + public boolean areForegroundServicesRestricted(int uid, @NonNull String packageName) { + synchronized (mLock) { + return isRunAnyRestrictedLocked(uid, packageName); + } + } + /** * @return whether force-app-standby is effective for a UID package-name. */ diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index eb4e32e474893..73b1e333d02b9 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -56,6 +56,8 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; +import com.android.server.AppStateTracker; +import com.android.server.LocalServices; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.ActivityManagerService.NeededUriGrants; import com.android.server.am.proto.ActiveServicesProto; @@ -164,6 +166,44 @@ public final class ActiveServices { } }; + /** + * Watch for apps being put into forced app standby, so we can step their fg + * services down. + */ + class ForcedStandbyListener extends AppStateTracker.Listener { + @Override + public void stopForegroundServicesForUidPackage(final int uid, final String packageName) { + synchronized (mAm) { + final ServiceMap smap = getServiceMapLocked(UserHandle.getUserId(uid)); + final int N = smap.mServicesByName.size(); + final ArrayList toStop = new ArrayList<>(N); + for (int i = 0; i < N; i++) { + final ServiceRecord r = smap.mServicesByName.valueAt(i); + if (uid == r.serviceInfo.applicationInfo.uid + || packageName.equals(r.serviceInfo.packageName)) { + if (r.isForeground) { + toStop.add(r); + } + } + } + + // Now stop them all + final int numToStop = toStop.size(); + if (numToStop > 0 && DEBUG_FOREGROUND_SERVICE) { + Slog.i(TAG, "Package " + packageName + "/" + uid + + " entering FAS with foreground services"); + } + for (int i = 0; i < numToStop; i++) { + final ServiceRecord r = toStop.get(i); + if (DEBUG_FOREGROUND_SERVICE) { + Slog.i(TAG, " Stopping fg for service " + r); + } + setServiceForegroundInnerLocked(r, 0, null, 0); + } + } + } + } + /** * Information about an app that is currently running one or more foreground services. * (This maps directly to the running apps we show in the notification.) @@ -302,6 +342,11 @@ public final class ActiveServices { ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 8; } + void systemServicesReady() { + AppStateTracker ast = LocalServices.getService(AppStateTracker.class); + ast.addListener(new ForcedStandbyListener()); + } + ServiceRecord getServiceByNameLocked(ComponentName name, int callingUser) { // TODO: Deal with global services if (DEBUG_MU) @@ -327,6 +372,12 @@ public final class ActiveServices { return getServiceMapLocked(callingUser).mServicesByName; } + private boolean appRestrictedAnyInBackground(final int uid, final String packageName) { + final int mode = mAm.mAppOpsService.checkOperation( + AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); + return (mode != AppOpsManager.MODE_ALLOWED); + } + ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { @@ -365,13 +416,24 @@ public final class ActiveServices { return null; } + // If the app has strict background restrictions, we treat any service + // start analogously to the legacy-app forced-restrictions case. + boolean forcedStandby = false; + if (appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG, "Forcing bg-only service start only for " + + r.name.flattenToShortString()); + } + forcedStandby = true; + } + // If this isn't a direct-to-foreground start, check our ability to kick off an // arbitrary service - if (!r.startRequested && !fgRequired) { + if (forcedStandby || (!r.startRequested && !fgRequired)) { // Before going further -- if this app is not allowed to start services in the // background, then at this point we aren't going to let it period. final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName, - r.appInfo.targetSdkVersion, callingPid, false, false); + r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby); if (allowed != ActivityManager.APP_START_MODE_NORMAL) { Slog.w(TAG, "Background start not allowed: service " + service + " to " + r.name.flattenToShortString() @@ -625,7 +687,7 @@ public final class ActiveServices { ServiceRecord service = services.mServicesByName.valueAt(i); if (service.appInfo.uid == uid && service.startRequested) { if (mAm.getAppStartModeLocked(service.appInfo.uid, service.packageName, - service.appInfo.targetSdkVersion, -1, false, false) + service.appInfo.targetSdkVersion, -1, false, false, false) != ActivityManager.APP_START_MODE_NORMAL) { if (stopping == null) { stopping = new ArrayList<>(); @@ -1019,7 +1081,10 @@ public final class ActiveServices { } } - private void setServiceForegroundInnerLocked(ServiceRecord r, int id, + /** + * @param id Notification ID. Zero === exit foreground state for the given service. + */ + private void setServiceForegroundInnerLocked(final ServiceRecord r, int id, Notification notification, int flags) { if (id != 0) { if (notification == null) { @@ -1061,44 +1126,56 @@ public final class ActiveServices { mAm.mHandler.removeMessages( ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); } - if (r.foregroundId != id) { - cancelForegroundNotificationLocked(r); - r.foregroundId = id; - } - notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; - r.foregroundNoti = notification; - if (!r.isForeground) { - final ServiceMap smap = getServiceMapLocked(r.userId); - if (smap != null) { - ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName); - if (active == null) { - active = new ActiveForegroundApp(); - active.mPackageName = r.packageName; - active.mUid = r.appInfo.uid; - active.mShownWhileScreenOn = mScreenOn; - if (r.app != null) { - active.mAppOnTop = active.mShownWhileTop = - r.app.uidRecord.curProcState - <= ActivityManager.PROCESS_STATE_TOP; - } - active.mStartTime = active.mStartVisibleTime - = SystemClock.elapsedRealtime(); - smap.mActiveForegroundApps.put(r.packageName, active); - requestUpdateActiveForegroundAppsLocked(smap, 0); - } - active.mNumActive++; + + // Apps under strict background restrictions simply don't get to have foreground + // services, so now that we've enforced the startForegroundService() contract + // we only do the machinery of making the service foreground when the app + // is not restricted. + if (!appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) { + if (r.foregroundId != id) { + cancelForegroundNotificationLocked(r); + r.foregroundId = id; + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.foregroundNoti = notification; + if (!r.isForeground) { + final ServiceMap smap = getServiceMapLocked(r.userId); + if (smap != null) { + ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName); + if (active == null) { + active = new ActiveForegroundApp(); + active.mPackageName = r.packageName; + active.mUid = r.appInfo.uid; + active.mShownWhileScreenOn = mScreenOn; + if (r.app != null) { + active.mAppOnTop = active.mShownWhileTop = + r.app.uidRecord.curProcState + <= ActivityManager.PROCESS_STATE_TOP; + } + active.mStartTime = active.mStartVisibleTime + = SystemClock.elapsedRealtime(); + smap.mActiveForegroundApps.put(r.packageName, active); + requestUpdateActiveForegroundAppsLocked(smap, 0); + } + active.mNumActive++; + } + r.isForeground = true; + StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, + r.appInfo.uid, r.shortName, + StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER); + } + r.postNotification(); + if (r.app != null) { + updateServiceForegroundLocked(r.app, true); + } + getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r); + mAm.notifyPackageUse(r.serviceInfo.packageName, + PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE); + } else { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG, "Suppressing startForeground() for FAS " + r); } - r.isForeground = true; - StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortName, - StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER); } - r.postNotification(); - if (r.app != null) { - updateServiceForegroundLocked(r.app, true); - } - getServiceMapLocked(r.userId).ensureNotStartingBackgroundLocked(r); - mAm.notifyPackageUse(r.serviceInfo.packageName, - PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE); } else { if (r.isForeground) { final ServiceMap smap = getServiceMapLocked(r.userId); @@ -1106,7 +1183,8 @@ public final class ActiveServices { decActiveForegroundAppLocked(smap, r); } r.isForeground = false; - StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, r.appInfo.uid, r.shortName, + StatsLog.write(StatsLog.FOREGROUND_SERVICE_STATE_CHANGED, + r.appInfo.uid, r.shortName, StatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT); if (r.app != null) { mAm.updateLruProcessLocked(r.app, false, null); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 04d46aa75ac4f..9f9844705baf5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -122,7 +122,6 @@ import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICAT import static android.text.format.DateUtils.DAY_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; - import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; @@ -203,7 +202,6 @@ import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; - import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -388,8 +386,8 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.View; import android.view.WindowManager; - import android.view.autofill.AutofillManagerInternal; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -2859,6 +2857,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mBatteryStatsService.systemServicesReady(); + mService.mServices.systemServicesReady(); } } @@ -9101,7 +9100,7 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean isAppStartModeDisabled(int uid, String packageName) { synchronized (this) { - return getAppStartModeLocked(uid, packageName, 0, -1, false, true) + return getAppStartModeLocked(uid, packageName, 0, -1, false, true, false) == ActivityManager.APP_START_MODE_DISABLED; } } @@ -9177,12 +9176,12 @@ public class ActivityManagerService extends IActivityManager.Stub } int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk, - int callingPid, boolean alwaysRestrict, boolean disabledOnly) { + int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) { UidRecord uidRec = mActiveUids.get(uid); if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg=" + packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle=" + (uidRec != null ? uidRec.idle : false)); - if (uidRec == null || alwaysRestrict || uidRec.idle) { + if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) { boolean ephemeral; if (uidRec == null) { ephemeral = getPackageManagerInternalLocked().isPackageEphemeral( diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index ea90db3f59686..9ae3861d33186 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1258,7 +1258,7 @@ public final class BroadcastQueue { if (!skip) { final int allowed = mService.getAppStartModeLocked( info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, - info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false); + info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false); if (allowed != ActivityManager.APP_START_MODE_NORMAL) { // We won't allow this receiver to be launched if the app has been // completely disabled from launches, or it was not explicitly sent