diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index ab610e4e71c6b..d962fa3bc316a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -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; @@ -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. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 71d6a48b89ee1..8041ec4a9727a 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -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; @@ -213,6 +215,9 @@ public final class ActiveServices { // at the same time. final int mMaxStartingBackground; + /** + * Master service bookkeeping, keyed by user number. + */ final SparseArray mServiceMap = new SparseArray<>(); /** @@ -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, @@ -1859,6 +1864,7 @@ public final class ActiveServices { active.mNumActive++; } r.isForeground = true; + r.mLogEntering = true; enterForeground = true; r.mStartForegroundCount++; r.mFgsEnterTime = SystemClock.uptimeMillis(); @@ -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); } @@ -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); @@ -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) { @@ -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) { - // Did the app have another FGS notification deferred recently? - showNow = withinFgsDeferRateLimit(uid, now); + if (!mAm.mConstants.mFlagFgsNotificationDeferralEnabled) { + return true; } - if (!showNow) { - // Legacy apps' FGS notifications are not deferred unless the relevant + + // 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? + if (withinFgsDeferRateLimit(r, now)) { + return true; + } + + 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; + } } - 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) { + + // did we already show it? + if (r.mFgsNotificationShown) { + return true; + } + + // has the app forced deferral? + if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { + // is the notification such that it should show right away? + if (r.foregroundNoti.shouldShowForegroundImmediately()) { + if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG_SERVICE, "FGS " + r + " notification policy says show immediately"); } - // 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; - } - } - } else { + return true; + } + + // or is this an type of FGS that always shows immediately? + if ((r.foregroundServiceType & FGS_IMMEDIATE_DISPLAY_MASK) != 0) { if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); + Slog.d(TAG_SERVICE, "FGS " + r + + " type gets immediate display"); } + return true; } - } - if (showNow) { + // fall through to return false: no policy dictates immediate display + } else { if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r + " non-deferred notification"); + Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); } - r.postNotification(); - r.mFgsNotificationDeferred = false; - r.mFgsNotificationShown = true; - return; + // fall through to return false } - r.mFgsNotificationDeferred = true; - r.mFgsNotificationShown = false; + return 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, @@ -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) @@ -2089,9 +2177,16 @@ public final class ActiveServices { if (r.isForeground && r.app != null) { r.postNotification(); r.mFgsNotificationShown = true; - } else if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, " - service no longer running/fg, ignoring"); + } 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) { @@ -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) { @@ -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); @@ -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; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 0fff8be705741..0d19efc207852 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -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; /** diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e6a0a8ec80dc..6e500e4ea5ea5 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index dbb2f65b06808..dd1ddd73a84f1 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -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 diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index dc9839c6da0ef..0528b95d1a6e6 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -30,6 +30,9 @@ public interface NotificationManagerInternal { void cancelNotification(String pkg, String basePkg, int callingUid, int callingPid, String tag, int id, int userId); + /** is the given notification currently showing? */ + boolean isNotificationShown(String pkg, String tag, int notificationId, int userId); + void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId); void onConversationRemoved(String pkg, int uid, Set shortcuts); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 0dd9b292b386b..15ddaf2575898 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -130,6 +130,7 @@ import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -3078,6 +3079,20 @@ public class NotificationManagerService extends SystemService { } } + protected void maybeReportForegroundServiceUpdate(final NotificationRecord r) { + if (r.isForegroundService()) { + // snapshot live state for the asynchronous operation + final StatusBarNotification sbn = r.getSbn(); + final Notification notification = sbn.getNotification(); + final int id = sbn.getId(); + final String pkg = sbn.getPackageName(); + final int userId = sbn.getUser().getIdentifier(); + mHandler.post(() -> { + mAmi.onForegroundServiceNotificationUpdate(notification, id, pkg, userId); + }); + } + } + private String getHistoryTitle(Notification n) { CharSequence title = null; if (n.extras != null) { @@ -5070,7 +5085,7 @@ public class NotificationManagerService extends SystemService { } @Override - public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {; + public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) { enforceSystemOrSystemUIOrSamePackage(pkg, "request policy access status for another package"); return checkPolicyAccess(pkg); @@ -5725,9 +5740,7 @@ public class NotificationManagerService extends SystemService { summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg); if (appIntent != null) { - final ActivityManagerInternal ami = LocalServices - .getService(ActivityManagerInternal.class); - summaryNotification.contentIntent = ami.getPendingIntentActivityAsApp( + summaryNotification.contentIntent = mAmi.getPendingIntentActivityAsApp( 0, appIntent, PendingIntent.FLAG_IMMUTABLE, null, pkg, appInfo.uid); } @@ -5778,7 +5791,7 @@ public class NotificationManagerService extends SystemService { return "callState"; } return null; - }; + } private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter) { JSONObject dump = new JSONObject(); @@ -6058,6 +6071,11 @@ public class NotificationManagerService extends SystemService { cancelNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, userId); } + @Override + public boolean isNotificationShown(String pkg, String tag, int notificationId, int userId) { + return isNotificationShownInternal(pkg, tag, notificationId, userId); + } + @Override public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId) { @@ -6141,16 +6159,22 @@ public class NotificationManagerService extends SystemService { mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null); } + boolean isNotificationShownInternal(String pkg, String tag, int notificationId, int userId) { + synchronized (mNotificationLock) { + return findNotificationLocked(pkg, tag, notificationId, userId) != null; + } + } + void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) { enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification, - incomingUserId, false); + incomingUserId, false); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, - final int callingPid, final String tag, final int id, final Notification notification, - int incomingUserId, boolean postSilently) { + final int callingPid, final String tag, final int id, final Notification notification, + int incomingUserId, boolean postSilently) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -6185,6 +6209,22 @@ public class NotificationManagerService extends SystemService { return; } + // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE, + // but it's also possible that the app has called notify() with an update to an + // FGS notification that hasn't yet been displayed. Make sure we check for any + // FGS-related situation up front, outside of any locks so it's safe to call into + // the Activity Manager. + final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification( + notification, id, pkg, userId); + if (policy == ServiceNotificationPolicy.UPDATE_ONLY) { + // Proceed if the notification is already showing/known, otherwise ignore + // because the service lifecycle logic has retained responsibility for its + // handling. + if (!isNotificationShownInternal(pkg, tag, id, userId)) { + return; + } + } + mUsageStats.registerEnqueuedByApp(pkg); final StatusBarNotification n = new StatusBarNotification( @@ -6287,19 +6327,17 @@ public class NotificationManagerService extends SystemService { if (notification.allPendingIntents != null) { final int intentCount = notification.allPendingIntents.size(); if (intentCount > 0) { - final ActivityManagerInternal am = LocalServices - .getService(ActivityManagerInternal.class); final long duration = LocalServices.getService( DeviceIdleInternal.class).getNotificationAllowlistDuration(); for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { - am.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), + mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), ALLOWLIST_TOKEN, duration, TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, "NotificationManagerService"); - am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), + mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); } @@ -7108,6 +7146,7 @@ public class NotificationManagerService extends SystemService { maybeRecordInterruptionLocked(r); maybeRegisterMessageSent(r); + maybeReportForegroundServiceUpdate(r); // Log event to statsd mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position, diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index e875065441889..6504ca683eb3f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -877,6 +877,10 @@ public final class NotificationRecord { return mHidden; } + public boolean isForegroundService() { + return 0 != (getFlags() & Notification.FLAG_FOREGROUND_SERVICE); + } + /** * Override of all alerting information on the channel and notification. Used when notifications * are reposted in response to direct user action and thus don't need to alert.