Loading core/java/android/app/ActivityManagerInternal.java +35 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,23 @@ import java.util.Set; */ public abstract class ActivityManagerInternal { public enum ServiceNotificationPolicy { /** * The Notification is not associated with any foreground service. */ NOT_FOREGROUND_SERVICE, /** * The Notification is associated with a foreground service, but the * notification system should handle it just like non-FGS notifications. */ SHOW_IMMEDIATELY, /** * The Notification is associated with a foreground service, and the * notification system should ignore it unless it has already been shown (in * which case it should be used to update the currently displayed UI). */ UPDATE_ONLY } // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; Loading Loading @@ -457,6 +474,24 @@ public abstract class ActivityManagerInternal { public abstract boolean hasForegroundServiceNotification(String pkg, @UserIdInt int userId, String channelId); /** * Tell the service lifecycle logic that the given Notification content is now * canonical for any foreground-service visibility policy purposes. * * Returns a description of any FGs-related policy around the given Notification: * not associated with an FGS; ensure display; or only update if already displayed. */ public abstract ServiceNotificationPolicy applyForegroundServiceNotification( Notification notification, int id, String pkg, @UserIdInt int userId); /** * Callback from the notification subsystem that the given FGS notification has * been shown or updated. This can happen after either Service.startForeground() * or NotificationManager.notify(). */ public abstract void onForegroundServiceNotificationUpdate(Notification notification, int id, String pkg, @UserIdInt int userId); /** * If the given app has any FGSs whose notifications are in the given channel, * stop them. Loading services/core/java/com/android/server/am/ActiveServices.java +203 −54 Original line number Diff line number Diff line Loading @@ -79,8 +79,10 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; Loading Loading @@ -213,6 +215,9 @@ public final class ActiveServices { // at the same time. final int mMaxStartingBackground; /** * Master service bookkeeping, keyed by user number. */ final SparseArray<ServiceMap> mServiceMap = new SparseArray<>(); /** Loading Loading @@ -1811,7 +1816,7 @@ public final class ActiveServices { showFgsBgRestrictedNotificationLocked(r); updateServiceForegroundLocked(psr, true); ignoreForeground = true; logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, 0); if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, Loading Loading @@ -1859,6 +1864,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; r.mLogEntering = true; enterForeground = true; r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); Loading @@ -1881,14 +1887,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. postFgsNotificationLocked(r); if (enterForeground) { // Because we want to log what's updated in postFgsNotificationLocked(), // this must be called after postFgsNotificationLocked(). logForegroundServiceStateChanged(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, 0); } r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(psr, true); } Loading Loading @@ -1937,7 +1936,7 @@ public final class ActiveServices { AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); Loading @@ -1964,7 +1963,18 @@ public final class ActiveServices { } } private boolean withinFgsDeferRateLimit(final int uid, final long now) { private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) { // If we're still within the service's deferral period, then by definition // deferral is not rate limited. if (now < sr.fgDisplayTime) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS transition for " + sr + " within deferral period, no rate limit applied"); } return false; } final int uid = sr.appInfo.uid; final long eligible = mFgsDeferralEligible.get(uid, 0L); if (DEBUG_FOREGROUND_SERVICE) { if (now < eligible) { Loading @@ -1975,62 +1985,137 @@ public final class ActiveServices { return now < eligible; } // TODO: remove as part of fixing b/173627642 ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification, final int id, final String pkg, final int userId) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Evaluating FGS policy for id=" + id + " pkg=" + pkg + " not=" + notification); } // Is there an FGS using this notification? final ServiceMap smap = mServiceMap.get(userId); if (smap == null) { // No services in this user at all return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; } for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); if (id != sr.foregroundId || !pkg.equals(sr.appInfo.packageName)) { // Not this one; keep looking continue; } // Found; it is associated with an FGS. Make sure that it's flagged: // it may have entered the bookkeeping outside of Service-related // APIs. We also make sure to take this latest Notification as // the content to be shown (immediately or eventually). if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " FOUND: notification is for " + sr); } notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; sr.foregroundNoti = notification; // ...and determine immediate vs deferred display policy for it final boolean showNow = shouldShowFgsNotificationLocked(sr); if (showNow) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " Showing immediately due to policy"); } sr.mFgsNotificationDeferred = false; return ServiceNotificationPolicy.SHOW_IMMEDIATELY; } // Deferring - kick off the timer if necessary, and tell the caller // that it's to be shown only if it's an update to already- // visible content (e.g. if it's an FGS adopting a // previously-posted Notification). if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " Deferring / update-only"); } startFgsDeferralTimerLocked(sr); return ServiceNotificationPolicy.UPDATE_ONLY; } // None of the services in this user are FGSs return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; } // No legacy-app behavior skew intended but there's a runtime E-stop if a need // arises, so note that @SuppressWarnings("AndroidFrameworkCompatChange") private void postFgsNotificationLocked(ServiceRecord r) { final int uid = r.appInfo.uid; private boolean shouldShowFgsNotificationLocked(ServiceRecord r) { final long now = SystemClock.uptimeMillis(); final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); // Is the behavior enabled at all? boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled; if (!showNow) { if (!mAm.mConstants.mFlagFgsNotificationDeferralEnabled) { return true; } // Has this service's deferral timer expired? if (r.mFgsNotificationDeferred && now >= r.fgDisplayTime) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG, "FGS reached end of deferral period: " + r); } return true; } // Did the app have another FGS notification deferred recently? showNow = withinFgsDeferRateLimit(uid, now); if (withinFgsDeferRateLimit(r, now)) { return true; } if (!showNow) { // Legacy apps' FGS notifications are not deferred unless the relevant if (mAm.mConstants.mFlagFgsNotificationDeferralApiGated) { // Legacy apps' FGS notifications are also deferred unless the relevant // DeviceConfig element has been set showNow = isLegacyApp && mAm.mConstants.mFlagFgsNotificationDeferralApiGated; final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); if (isLegacyApp) { return true; } } // did we already show it? if (r.mFgsNotificationShown) { return true; } if (!showNow) { // has the app forced deferral? if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { // is the notification such that it should show right away? showNow = r.foregroundNoti.shouldShowForegroundImmediately(); if (DEBUG_FOREGROUND_SERVICE && showNow) { if (r.foregroundNoti.shouldShowForegroundImmediately()) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification policy says show immediately"); } return true; } // or is this an type of FGS that always shows immediately? if (!showNow) { if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " type gets immediate display"); } showNow = true; } return true; } // fall through to return false: no policy dictates immediate display } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); } } // fall through to return false } if (showNow) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " non-deferred notification"); } r.postNotification(); r.mFgsNotificationDeferred = false; r.mFgsNotificationShown = true; return; return false; } r.mFgsNotificationDeferred = true; r.mFgsNotificationShown = false; // Target SDK consultation here is strictly for logging purposes, not // behavioral variation. @SuppressWarnings("AndroidFrameworkCompatChange") private void startFgsDeferralTimerLocked(ServiceRecord r) { final long now = SystemClock.uptimeMillis(); final int uid = r.appInfo.uid; // schedule the actual notification post long when = now + mAm.mConstants.mFgsNotificationDeferralInterval; // If there are already deferred FGS notifications for this app, Loading @@ -2053,11 +2138,14 @@ public final class ActiveServices { final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime; mFgsDeferralEligible.put(uid, nextEligible); r.fgDisplayTime = when; r.mFgsNotificationDeferred = true; r.mFgsNotificationShown = false; mPendingFgsNotifications.add(r); if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification in " + (when - now) + " ms"); } final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); if (isLegacyApp) { Slog.i(TAG_SERVICE, "Deferring FGS notification in legacy app " + r.appInfo.packageName + "/" + UserHandle.formatUid(r.appInfo.uid) Loading Loading @@ -2089,10 +2177,17 @@ public final class ActiveServices { if (r.isForeground && r.app != null) { r.postNotification(); r.mFgsNotificationShown = true; } else if (DEBUG_FOREGROUND_SERVICE) { } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); } } // Regardless of whether we needed to post the notification or the // service is no longer running, we may not have logged its FGS // transition yet depending on the timing and API sequence that led // to this point - so make sure to do so. maybeLogFGSStateEnteredLocked(r); } } if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Done evaluating deferred FGS notifications; " Loading @@ -2102,6 +2197,60 @@ public final class ActiveServices { } }; private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { if (r.mLogEntering) { logFGSStateChangeLocked(r, FrameworkStatsLog .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, 0); r.mLogEntering = false; } } /** * Callback from NotificationManagerService whenever it posts a notification * associated with a foreground service. This is the unified handling point * for the disjoint code flows that affect an FGS's notifiation content and * visibility, starting with both Service.startForeground() and * NotificationManager.notify(). */ public void onForegroundServiceNotificationUpdateLocked(Notification notification, final int id, final String pkg, @UserIdInt final int userId) { // If this happens to be a Notification for an FGS still in its deferral period, // drop the deferral and make sure our content bookkeeping is up to date. for (int i = mPendingFgsNotifications.size() - 1; i >= 0; i--) { final ServiceRecord sr = mPendingFgsNotifications.get(i); if (userId == sr.userId && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of " + sr); } maybeLogFGSStateEnteredLocked(sr); sr.mFgsNotificationShown = true; sr.mFgsNotificationDeferred = false; mPendingFgsNotifications.remove(i); } } // And make sure to retain the latest notification content for the FGS ServiceMap smap = mServiceMap.get(userId); if (smap != null) { for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); if (sr.isForeground && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Recording shown notification for " + sr); } sr.foregroundNoti = notification; } } } } /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */ private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) { if (r.app == null) { Loading Loading @@ -4016,7 +4165,7 @@ public final class ActiveServices { AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); r.mFgsExitTime = SystemClock.uptimeMillis(); logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); Loading Loading @@ -6022,7 +6171,7 @@ public final class ActiveServices { * @param state one of ENTER/EXIT/DENIED event. * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state. */ private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) { private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mFgsAtomSampleRate)) { return; Loading services/core/java/com/android/server/am/ActivityManagerConstants.java +1 −1 Original line number Diff line number Diff line Loading @@ -453,7 +453,7 @@ final class ActivityManagerConstants extends ContentObserver { volatile long mFgsNotificationDeferralInterval = 10_000; // Rate limit: minimum time after an app's FGS notification is deferred // before another FGS notifiction from that app can be deferred. // before another FGS notification from that app can be deferred. volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L; /** Loading services/core/java/com/android/server/am/ActivityManagerService.java +18 −0 Original line number Diff line number Diff line Loading @@ -16058,6 +16058,24 @@ public class ActivityManagerService extends IActivityManager.Stub } } @Override public ServiceNotificationPolicy applyForegroundServiceNotification( Notification notification, int id, String pkg, int userId) { synchronized (ActivityManagerService.this) { return mServices.applyForegroundServiceNotificationLocked(notification, id, pkg, userId); } } @Override public void onForegroundServiceNotificationUpdate(Notification notification, int id, String pkg, @UserIdInt int userId) { synchronized (ActivityManagerService.this) { mServices.onForegroundServiceNotificationUpdateLocked(notification, id, pkg, userId); } } @Override public void stopForegroundServicesForChannel(String pkg, int userId, String channelId) { services/core/java/com/android/server/am/ServiceRecord.java +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? boolean mLogEntering; // need to report fgs transition once deferral policy is known int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. long fgDisplayTime; // time at which the FGS notification should become visible Loading Loading
core/java/android/app/ActivityManagerInternal.java +35 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,23 @@ import java.util.Set; */ public abstract class ActivityManagerInternal { public enum ServiceNotificationPolicy { /** * The Notification is not associated with any foreground service. */ NOT_FOREGROUND_SERVICE, /** * The Notification is associated with a foreground service, but the * notification system should handle it just like non-FGS notifications. */ SHOW_IMMEDIATELY, /** * The Notification is associated with a foreground service, and the * notification system should ignore it unless it has already been shown (in * which case it should be used to update the currently displayed UI). */ UPDATE_ONLY } // Access modes for handleIncomingUser. public static final int ALLOW_NON_FULL = 0; Loading Loading @@ -457,6 +474,24 @@ public abstract class ActivityManagerInternal { public abstract boolean hasForegroundServiceNotification(String pkg, @UserIdInt int userId, String channelId); /** * Tell the service lifecycle logic that the given Notification content is now * canonical for any foreground-service visibility policy purposes. * * Returns a description of any FGs-related policy around the given Notification: * not associated with an FGS; ensure display; or only update if already displayed. */ public abstract ServiceNotificationPolicy applyForegroundServiceNotification( Notification notification, int id, String pkg, @UserIdInt int userId); /** * Callback from the notification subsystem that the given FGS notification has * been shown or updated. This can happen after either Service.startForeground() * or NotificationManager.notify(). */ public abstract void onForegroundServiceNotificationUpdate(Notification notification, int id, String pkg, @UserIdInt int userId); /** * If the given app has any FGSs whose notifications are in the given channel, * stop them. Loading
services/core/java/com/android/server/am/ActiveServices.java +203 −54 Original line number Diff line number Diff line Loading @@ -79,8 +79,10 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; import android.app.AppOpsManager; Loading Loading @@ -213,6 +215,9 @@ public final class ActiveServices { // at the same time. final int mMaxStartingBackground; /** * Master service bookkeeping, keyed by user number. */ final SparseArray<ServiceMap> mServiceMap = new SparseArray<>(); /** Loading Loading @@ -1811,7 +1816,7 @@ public final class ActiveServices { showFgsBgRestrictedNotificationLocked(r); updateServiceForegroundLocked(psr, true); ignoreForeground = true; logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED, 0); if (CompatChanges.isChangeEnabled(FGS_START_EXCEPTION_CHANGE_ID, Loading Loading @@ -1859,6 +1864,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; r.mLogEntering = true; enterForeground = true; r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); Loading @@ -1881,14 +1887,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. postFgsNotificationLocked(r); if (enterForeground) { // Because we want to log what's updated in postFgsNotificationLocked(), // this must be called after postFgsNotificationLocked(). logForegroundServiceStateChanged(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, 0); } r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(psr, true); } Loading Loading @@ -1937,7 +1936,7 @@ public final class ActiveServices { AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); Loading @@ -1964,7 +1963,18 @@ public final class ActiveServices { } } private boolean withinFgsDeferRateLimit(final int uid, final long now) { private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) { // If we're still within the service's deferral period, then by definition // deferral is not rate limited. if (now < sr.fgDisplayTime) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS transition for " + sr + " within deferral period, no rate limit applied"); } return false; } final int uid = sr.appInfo.uid; final long eligible = mFgsDeferralEligible.get(uid, 0L); if (DEBUG_FOREGROUND_SERVICE) { if (now < eligible) { Loading @@ -1975,62 +1985,137 @@ public final class ActiveServices { return now < eligible; } // TODO: remove as part of fixing b/173627642 ServiceNotificationPolicy applyForegroundServiceNotificationLocked(Notification notification, final int id, final String pkg, final int userId) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Evaluating FGS policy for id=" + id + " pkg=" + pkg + " not=" + notification); } // Is there an FGS using this notification? final ServiceMap smap = mServiceMap.get(userId); if (smap == null) { // No services in this user at all return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; } for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); if (id != sr.foregroundId || !pkg.equals(sr.appInfo.packageName)) { // Not this one; keep looking continue; } // Found; it is associated with an FGS. Make sure that it's flagged: // it may have entered the bookkeeping outside of Service-related // APIs. We also make sure to take this latest Notification as // the content to be shown (immediately or eventually). if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " FOUND: notification is for " + sr); } notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; sr.foregroundNoti = notification; // ...and determine immediate vs deferred display policy for it final boolean showNow = shouldShowFgsNotificationLocked(sr); if (showNow) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " Showing immediately due to policy"); } sr.mFgsNotificationDeferred = false; return ServiceNotificationPolicy.SHOW_IMMEDIATELY; } // Deferring - kick off the timer if necessary, and tell the caller // that it's to be shown only if it's an update to already- // visible content (e.g. if it's an FGS adopting a // previously-posted Notification). if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " Deferring / update-only"); } startFgsDeferralTimerLocked(sr); return ServiceNotificationPolicy.UPDATE_ONLY; } // None of the services in this user are FGSs return ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE; } // No legacy-app behavior skew intended but there's a runtime E-stop if a need // arises, so note that @SuppressWarnings("AndroidFrameworkCompatChange") private void postFgsNotificationLocked(ServiceRecord r) { final int uid = r.appInfo.uid; private boolean shouldShowFgsNotificationLocked(ServiceRecord r) { final long now = SystemClock.uptimeMillis(); final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); // Is the behavior enabled at all? boolean showNow = !mAm.mConstants.mFlagFgsNotificationDeferralEnabled; if (!showNow) { if (!mAm.mConstants.mFlagFgsNotificationDeferralEnabled) { return true; } // Has this service's deferral timer expired? if (r.mFgsNotificationDeferred && now >= r.fgDisplayTime) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG, "FGS reached end of deferral period: " + r); } return true; } // Did the app have another FGS notification deferred recently? showNow = withinFgsDeferRateLimit(uid, now); if (withinFgsDeferRateLimit(r, now)) { return true; } if (!showNow) { // Legacy apps' FGS notifications are not deferred unless the relevant if (mAm.mConstants.mFlagFgsNotificationDeferralApiGated) { // Legacy apps' FGS notifications are also deferred unless the relevant // DeviceConfig element has been set showNow = isLegacyApp && mAm.mConstants.mFlagFgsNotificationDeferralApiGated; final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); if (isLegacyApp) { return true; } } // did we already show it? if (r.mFgsNotificationShown) { return true; } if (!showNow) { // has the app forced deferral? if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { // is the notification such that it should show right away? showNow = r.foregroundNoti.shouldShowForegroundImmediately(); if (DEBUG_FOREGROUND_SERVICE && showNow) { if (r.foregroundNoti.shouldShowForegroundImmediately()) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification policy says show immediately"); } return true; } // or is this an type of FGS that always shows immediately? if (!showNow) { if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " type gets immediate display"); } showNow = true; } return true; } // fall through to return false: no policy dictates immediate display } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); } } // fall through to return false } if (showNow) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " non-deferred notification"); } r.postNotification(); r.mFgsNotificationDeferred = false; r.mFgsNotificationShown = true; return; return false; } r.mFgsNotificationDeferred = true; r.mFgsNotificationShown = false; // Target SDK consultation here is strictly for logging purposes, not // behavioral variation. @SuppressWarnings("AndroidFrameworkCompatChange") private void startFgsDeferralTimerLocked(ServiceRecord r) { final long now = SystemClock.uptimeMillis(); final int uid = r.appInfo.uid; // schedule the actual notification post long when = now + mAm.mConstants.mFgsNotificationDeferralInterval; // If there are already deferred FGS notifications for this app, Loading @@ -2053,11 +2138,14 @@ public final class ActiveServices { final long nextEligible = when + mAm.mConstants.mFgsNotificationDeferralExclusionTime; mFgsDeferralEligible.put(uid, nextEligible); r.fgDisplayTime = when; r.mFgsNotificationDeferred = true; r.mFgsNotificationShown = false; mPendingFgsNotifications.add(r); if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification in " + (when - now) + " ms"); } final boolean isLegacyApp = (r.appInfo.targetSdkVersion < Build.VERSION_CODES.S); if (isLegacyApp) { Slog.i(TAG_SERVICE, "Deferring FGS notification in legacy app " + r.appInfo.packageName + "/" + UserHandle.formatUid(r.appInfo.uid) Loading Loading @@ -2089,10 +2177,17 @@ public final class ActiveServices { if (r.isForeground && r.app != null) { r.postNotification(); r.mFgsNotificationShown = true; } else if (DEBUG_FOREGROUND_SERVICE) { } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); } } // Regardless of whether we needed to post the notification or the // service is no longer running, we may not have logged its FGS // transition yet depending on the timing and API sequence that led // to this point - so make sure to do so. maybeLogFGSStateEnteredLocked(r); } } if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Done evaluating deferred FGS notifications; " Loading @@ -2102,6 +2197,60 @@ public final class ActiveServices { } }; private void maybeLogFGSStateEnteredLocked(ServiceRecord r) { if (r.mLogEntering) { logFGSStateChangeLocked(r, FrameworkStatsLog .FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER, 0); r.mLogEntering = false; } } /** * Callback from NotificationManagerService whenever it posts a notification * associated with a foreground service. This is the unified handling point * for the disjoint code flows that affect an FGS's notifiation content and * visibility, starting with both Service.startForeground() and * NotificationManager.notify(). */ public void onForegroundServiceNotificationUpdateLocked(Notification notification, final int id, final String pkg, @UserIdInt final int userId) { // If this happens to be a Notification for an FGS still in its deferral period, // drop the deferral and make sure our content bookkeeping is up to date. for (int i = mPendingFgsNotifications.size() - 1; i >= 0; i--) { final ServiceRecord sr = mPendingFgsNotifications.get(i); if (userId == sr.userId && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Notification shown; canceling deferral of " + sr); } maybeLogFGSStateEnteredLocked(sr); sr.mFgsNotificationShown = true; sr.mFgsNotificationDeferred = false; mPendingFgsNotifications.remove(i); } } // And make sure to retain the latest notification content for the FGS ServiceMap smap = mServiceMap.get(userId); if (smap != null) { for (int i = 0; i < smap.mServicesByInstanceName.size(); i++) { final ServiceRecord sr = smap.mServicesByInstanceName.valueAt(i); if (sr.isForeground && id == sr.foregroundId && sr.appInfo.packageName.equals(pkg)) { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "Recording shown notification for " + sr); } sr.foregroundNoti = notification; } } } } /** Registers an AppOpCallback for monitoring special AppOps for this foreground service. */ private void registerAppOpCallbackLocked(@NonNull ServiceRecord r) { if (r.app == null) { Loading Loading @@ -4016,7 +4165,7 @@ public final class ActiveServices { AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); unregisterAppOpCallbackLocked(r); r.mFgsExitTime = SystemClock.uptimeMillis(); logForegroundServiceStateChanged(r, logFGSStateChangeLocked(r, FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT, r.mFgsExitTime > r.mFgsEnterTime ? (int)(r.mFgsExitTime - r.mFgsEnterTime) : 0); Loading Loading @@ -6022,7 +6171,7 @@ public final class ActiveServices { * @param state one of ENTER/EXIT/DENIED event. * @param durationMs Only meaningful for EXIT event, the duration from ENTER and EXIT state. */ private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) { private void logFGSStateChangeLocked(ServiceRecord r, int state, int durationMs) { if (!ActivityManagerUtils.shouldSamplePackageForAtom( r.packageName, mAm.mConstants.mFgsAtomSampleRate)) { return; Loading
services/core/java/com/android/server/am/ActivityManagerConstants.java +1 −1 Original line number Diff line number Diff line Loading @@ -453,7 +453,7 @@ final class ActivityManagerConstants extends ContentObserver { volatile long mFgsNotificationDeferralInterval = 10_000; // Rate limit: minimum time after an app's FGS notification is deferred // before another FGS notifiction from that app can be deferred. // before another FGS notification from that app can be deferred. volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L; /** Loading
services/core/java/com/android/server/am/ActivityManagerService.java +18 −0 Original line number Diff line number Diff line Loading @@ -16058,6 +16058,24 @@ public class ActivityManagerService extends IActivityManager.Stub } } @Override public ServiceNotificationPolicy applyForegroundServiceNotification( Notification notification, int id, String pkg, int userId) { synchronized (ActivityManagerService.this) { return mServices.applyForegroundServiceNotificationLocked(notification, id, pkg, userId); } } @Override public void onForegroundServiceNotificationUpdate(Notification notification, int id, String pkg, @UserIdInt int userId) { synchronized (ActivityManagerService.this) { mServices.onForegroundServiceNotificationUpdateLocked(notification, id, pkg, userId); } } @Override public void stopForegroundServicesForChannel(String pkg, int userId, String channelId) {
services/core/java/com/android/server/am/ServiceRecord.java +1 −0 Original line number Diff line number Diff line Loading @@ -109,6 +109,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isNotAppComponentUsage; // is service binding not considered component/package usage? boolean isForeground; // is service currently in foreground mode? boolean mLogEntering; // need to report fgs transition once deferral policy is known int foregroundId; // Notification ID of last foreground req. Notification foregroundNoti; // Notification record of foreground state. long fgDisplayTime; // time at which the FGS notification should become visible Loading