Merge "Fix various FGS notification deferral issues" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
2ee596d203
@@ -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.
|
||||
|
||||
@@ -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<ServiceMap> 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> shortcuts);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user