Don't prompt on bg abuse if the app has a FGS with notification
Add a new config to control whether or not to prompt the user when the system detects a background abusive app and it has a foreground service with notification shown. By default it's OFF, meaning if the background abusive app has a foreground service and its notification is still showing, no prompt to the user in this case. Bug: 200326767 Bug: 203105544 Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest Change-Id: Ic611090e3152117a2379d902fc32416e11c3e197
This commit is contained in:
@@ -779,6 +779,16 @@ public abstract class ActivityManagerInternal {
|
||||
* @param started {@code true} if the process transits from non-FGS state to FGS state.
|
||||
*/
|
||||
void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started);
|
||||
|
||||
/**
|
||||
* Call when the notification of the foreground service is updated.
|
||||
*
|
||||
* @param packageName The package name of the process.
|
||||
* @param uid The UID of the process.
|
||||
* @param foregroundId The current foreground service notification ID, a negative value
|
||||
* means this notification is being removed.
|
||||
*/
|
||||
void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5758,4 +5758,10 @@
|
||||
current drain threshold.
|
||||
-->
|
||||
<integer name="config_bg_current_drain_location_min_duration">1800</integer>
|
||||
|
||||
<!-- The behavior for an app with a FGS and its notification is still showing, when the system
|
||||
detects it's abusive and should be put into bg restricted level. True - we'll
|
||||
show the prompt to user, False - we'll not show it.
|
||||
-->
|
||||
<bool name="config_bg_prompt_fgs_with_noti_to_bg_restricted">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -4743,4 +4743,5 @@
|
||||
<java-symbol type="array" name="config_bg_current_drain_high_threshold_to_bg_restricted" />
|
||||
<java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" />
|
||||
<java-symbol type="integer" name="config_bg_current_drain_location_min_duration" />
|
||||
<java-symbol type="bool" name="config_bg_prompt_fgs_with_noti_to_bg_restricted" />
|
||||
</resources>
|
||||
|
||||
@@ -1454,16 +1454,18 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
|
||||
notifyController = true;
|
||||
} else {
|
||||
excessive = true;
|
||||
if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
|
||||
if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]
|
||||
&& curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
|
||||
// If we're in the restricted standby bucket but still seeing high
|
||||
// current drains, tell the controller again.
|
||||
if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
|
||||
&& ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) {
|
||||
if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
|
||||
+ mBgCurrentDrainWindowMs) {
|
||||
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
|
||||
notifyController = true;
|
||||
}
|
||||
final long lastResbucket = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
|
||||
final long lastBgRes = ts[TIME_STAMP_INDEX_BG_RESTRICTED];
|
||||
// If it has been a while since restricting the app and since the last
|
||||
// time we notify the controller, notify it again.
|
||||
if ((now >= lastResbucket + mBgCurrentDrainWindowMs) && (lastBgRes == 0
|
||||
|| (now >= lastBgRes + mBgCurrentDrainWindowMs))) {
|
||||
ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
|
||||
notifyController = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TimeUtils;
|
||||
@@ -54,6 +55,7 @@ import com.android.server.am.AppFGSTracker.PackageDurations;
|
||||
import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
|
||||
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
|
||||
import com.android.server.am.BaseAppStateTracker.Injector;
|
||||
import com.android.server.notification.NotificationManagerInternal;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -71,6 +73,9 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
|
||||
private final MyHandler mHandler;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final UidProcessMap<ArraySet<Integer>> mFGSNotificationIDs = new UidProcessMap<>();
|
||||
|
||||
// Unlocked since it's only accessed in single thread.
|
||||
private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>();
|
||||
|
||||
@@ -100,11 +105,19 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
: MyHandler.MSG_FOREGROUND_SERVICES_STOPPED, pid, uid, packageName).sendToTarget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onForegroundServiceNotificationUpdated(String packageName, int uid,
|
||||
int foregroundId) {
|
||||
mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
|
||||
uid, foregroundId, packageName).sendToTarget();
|
||||
}
|
||||
|
||||
private static class MyHandler extends Handler {
|
||||
static final int MSG_FOREGROUND_SERVICES_STARTED = 0;
|
||||
static final int MSG_FOREGROUND_SERVICES_STOPPED = 1;
|
||||
static final int MSG_FOREGROUND_SERVICES_CHANGED = 2;
|
||||
static final int MSG_CHECK_LONG_RUNNING_FGS = 3;
|
||||
static final int MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED = 3;
|
||||
static final int MSG_CHECK_LONG_RUNNING_FGS = 4;
|
||||
|
||||
private final AppFGSTracker mTracker;
|
||||
|
||||
@@ -128,6 +141,10 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
mTracker.handleForegroundServicesChanged(
|
||||
(String) msg.obj, msg.arg1, msg.arg2);
|
||||
break;
|
||||
case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
|
||||
mTracker.handleForegroundServiceNotificationUpdated(
|
||||
(String) msg.obj, msg.arg1, msg.arg2);
|
||||
break;
|
||||
case MSG_CHECK_LONG_RUNNING_FGS:
|
||||
mTracker.checkLongRunningFgs();
|
||||
break;
|
||||
@@ -205,6 +222,44 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
}
|
||||
}
|
||||
|
||||
private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
|
||||
int notificationId) {
|
||||
synchronized (mLock) {
|
||||
if (notificationId > 0) {
|
||||
ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName);
|
||||
if (notificationIDs == null) {
|
||||
notificationIDs = new ArraySet<>();
|
||||
mFGSNotificationIDs.put(uid, packageName, notificationIDs);
|
||||
}
|
||||
notificationIDs.add(notificationId);
|
||||
} else if (notificationId < 0) {
|
||||
final ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName);
|
||||
if (notificationIDs != null) {
|
||||
notificationIDs.remove(-notificationId);
|
||||
if (notificationIDs.isEmpty()) {
|
||||
mFGSNotificationIDs.remove(uid, packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean hasForegroundServiceNotificationsLocked(String packageName, int uid) {
|
||||
final ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName);
|
||||
if (notificationIDs == null || notificationIDs.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
final NotificationManagerInternal nm = mInjector.getNotificationManagerInternal();
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
for (int i = notificationIDs.size() - 1; i >= 0; i--) {
|
||||
if (nm.isNotificationShown(packageName, null, notificationIDs.valueAt(i), userId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void scheduleDurationCheckLocked(long now) {
|
||||
// Look for the active FGS with longest running time till now.
|
||||
@@ -375,6 +430,28 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasForegroundServiceNotifications(String packageName, int uid) {
|
||||
synchronized (mLock) {
|
||||
return hasForegroundServiceNotificationsLocked(packageName, uid);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasForegroundServiceNotifications(int uid) {
|
||||
synchronized (mLock) {
|
||||
final SparseArray<ArrayMap<String, ArraySet<Integer>>> map =
|
||||
mFGSNotificationIDs.getMap();
|
||||
final ArrayMap<String, ArraySet<Integer>> pkgs = map.get(uid);
|
||||
if (pkgs != null) {
|
||||
for (int i = pkgs.size() - 1; i >= 0; i--) {
|
||||
if (hasForegroundServiceNotificationsLocked(pkgs.keyAt(i), uid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix);
|
||||
@@ -382,6 +459,35 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
|
||||
super.dump(pw, " " + prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
void dumpOthers(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix);
|
||||
pw.println("APPS WITH ACTIVE FOREGROUND SERVICES:");
|
||||
prefix = " " + prefix;
|
||||
synchronized (mLock) {
|
||||
final SparseArray<ArrayMap<String, ArraySet<Integer>>> map =
|
||||
mFGSNotificationIDs.getMap();
|
||||
if (map.size() == 0) {
|
||||
pw.print(prefix);
|
||||
pw.println("(none)");
|
||||
}
|
||||
for (int i = 0, size = map.size(); i < size; i++) {
|
||||
final int uid = map.keyAt(i);
|
||||
final String uidString = UserHandle.formatUid(uid);
|
||||
final ArrayMap<String, ArraySet<Integer>> pkgs = map.valueAt(i);
|
||||
for (int j = 0, numOfPkgs = pkgs.size(); j < numOfPkgs; j++) {
|
||||
final String pkgName = pkgs.keyAt(j);
|
||||
pw.print(prefix);
|
||||
pw.print(pkgName);
|
||||
pw.print('/');
|
||||
pw.print(uidString);
|
||||
pw.print(" notification=");
|
||||
pw.println(hasForegroundServiceNotificationsLocked(pkgName, uid));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the durations with active FGS for a given package.
|
||||
*/
|
||||
|
||||
@@ -547,17 +547,37 @@ public final class AppRestrictionController {
|
||||
static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
|
||||
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
|
||||
|
||||
/**
|
||||
* The behavior for an app with a FGS and its notification is still showing, when the system
|
||||
* detects it's abusive and should be put into bg restricted level. {@code true} - we'll
|
||||
* show the prompt to user, {@code false} - we'll not show it.
|
||||
*/
|
||||
static final String KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED =
|
||||
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_with_noti_to_bg_restricted";
|
||||
|
||||
static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false;
|
||||
static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Default value to {@link #mBgPromptFgsWithNotiToBgRestricted}.
|
||||
*/
|
||||
final boolean mDefaultBgPromptFgsWithNotiToBgRestricted;
|
||||
|
||||
volatile boolean mBgAutoRestrictedBucket;
|
||||
|
||||
volatile boolean mRestrictedBucketEnabled;
|
||||
|
||||
volatile long mBgNotificationMinIntervalMs;
|
||||
|
||||
ConstantsObserver(Handler handler) {
|
||||
/**
|
||||
* @see #KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED.
|
||||
*/
|
||||
volatile boolean mBgPromptFgsWithNotiToBgRestricted;
|
||||
|
||||
ConstantsObserver(Handler handler, Context context) {
|
||||
super(handler);
|
||||
mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean(
|
||||
com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -573,6 +593,9 @@ public final class AppRestrictionController {
|
||||
case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL:
|
||||
updateBgAbusiveNotificationMinimalInterval();
|
||||
break;
|
||||
case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED:
|
||||
updateBgPromptFgsWithNotiToBgRestricted();
|
||||
break;
|
||||
}
|
||||
AppRestrictionController.this.onPropertiesChanged(name);
|
||||
}
|
||||
@@ -604,6 +627,7 @@ public final class AppRestrictionController {
|
||||
void updateDeviceConfig() {
|
||||
updateBgAutoRestrictedBucketChanged();
|
||||
updateBgAbusiveNotificationMinimalInterval();
|
||||
updateBgPromptFgsWithNotiToBgRestricted();
|
||||
}
|
||||
|
||||
private void updateBgAutoRestrictedBucketChanged() {
|
||||
@@ -623,6 +647,32 @@ public final class AppRestrictionController {
|
||||
KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL,
|
||||
DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private void updateBgPromptFgsWithNotiToBgRestricted() {
|
||||
mBgPromptFgsWithNotiToBgRestricted = DeviceConfig.getBoolean(
|
||||
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
||||
KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED,
|
||||
mDefaultBgPromptFgsWithNotiToBgRestricted);
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix);
|
||||
pw.println("BACKGROUND RESTRICTION POLICY SETTINGS:");
|
||||
final String indent = " ";
|
||||
prefix = indent + prefix;
|
||||
pw.print(prefix);
|
||||
pw.print(KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
|
||||
pw.print('=');
|
||||
pw.println(mBgAutoRestrictedBucket);
|
||||
pw.print(prefix);
|
||||
pw.print(KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL);
|
||||
pw.print('=');
|
||||
pw.println(mBgNotificationMinIntervalMs);
|
||||
pw.print(prefix);
|
||||
pw.print(KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED);
|
||||
pw.print('=');
|
||||
pw.println(mBgPromptFgsWithNotiToBgRestricted);
|
||||
}
|
||||
}
|
||||
|
||||
private final ConstantsObserver mConstantsObserver;
|
||||
@@ -704,7 +754,7 @@ public final class AppRestrictionController {
|
||||
mBgHandlerThread.start();
|
||||
mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
|
||||
mBgExecutor = new HandlerExecutor(mBgHandler);
|
||||
mConstantsObserver = new ConstantsObserver(mBgHandler);
|
||||
mConstantsObserver = new ConstantsObserver(mBgHandler, mContext);
|
||||
mNotificationHelper = new NotificationHelper(this);
|
||||
injector.initAppStateTrackers(this);
|
||||
}
|
||||
@@ -1073,6 +1123,20 @@ public final class AppRestrictionController {
|
||||
return mInjector.getAppFGSTracker().hasForegroundServices(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the given package/uid has a foreground service notification or not.
|
||||
*/
|
||||
boolean hasForegroundServiceNotifications(String packageName, int uid) {
|
||||
return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(packageName, uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the given uid has a foreground service notification or not.
|
||||
*/
|
||||
boolean hasForegroundServiceNotifications(int uid) {
|
||||
return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The to-be-exempted battery usage of the given UID in the given duration; it could
|
||||
* be considered as "exempted" due to various use cases, i.e. media playback.
|
||||
@@ -1098,11 +1162,14 @@ public final class AppRestrictionController {
|
||||
|
||||
void dump(PrintWriter pw, String prefix) {
|
||||
pw.print(prefix);
|
||||
pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
|
||||
pw.println("APP BACKGROUND RESTRICTIONS");
|
||||
prefix = " " + prefix;
|
||||
pw.print(prefix);
|
||||
pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
|
||||
synchronized (mLock) {
|
||||
mRestrictionSettings.dumpLocked(pw, prefix);
|
||||
mRestrictionSettings.dumpLocked(pw, " " + prefix);
|
||||
}
|
||||
mConstantsObserver.dump(pw, " " + prefix);
|
||||
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
|
||||
pw.println();
|
||||
mAppStateTrackers.get(i).dump(pw, prefix);
|
||||
@@ -1366,8 +1433,21 @@ public final class AppRestrictionController {
|
||||
intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null,
|
||||
UserHandle.of(UserHandle.getUserId(uid)));
|
||||
Notification.Action[] actions = null;
|
||||
if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER
|
||||
&& mBgController.hasForegroundServices(packageName, uid)) {
|
||||
final boolean hasForegroundServices =
|
||||
mBgController.hasForegroundServices(packageName, uid);
|
||||
final boolean hasForegroundServiceNotifications =
|
||||
mBgController.hasForegroundServiceNotifications(packageName, uid);
|
||||
if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiToBgRestricted) {
|
||||
// We're not going to prompt the user if the FGS is active and its notification
|
||||
// is still showing (not dismissed/silenced/denied).
|
||||
if (hasForegroundServices && hasForegroundServiceNotifications) {
|
||||
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
|
||||
Slog.i(TAG, "Not requesting bg-restriction due to FGS with notification");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER && hasForegroundServices) {
|
||||
final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE);
|
||||
trampoline.setPackage("android");
|
||||
trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
|
||||
|
||||
@@ -184,9 +184,13 @@ abstract class BaseAppStateEventsTracker
|
||||
}
|
||||
}
|
||||
}
|
||||
dumpOthers(pw, prefix);
|
||||
policy.dump(pw, prefix);
|
||||
}
|
||||
|
||||
void dumpOthers(PrintWriter pw, String prefix) {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
void dumpEventHeaderLocked(PrintWriter pw, String prefix, String packageName, int uid, U events,
|
||||
T policy) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.util.Slog;
|
||||
|
||||
import com.android.server.DeviceIdleInternal;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.notification.NotificationManagerInternal;
|
||||
import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal;
|
||||
|
||||
@@ -183,6 +184,7 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
|
||||
AppOpsManager mAppOpsManager;
|
||||
MediaSessionManager mMediaSessionManager;
|
||||
RoleManager mRoleManager;
|
||||
NotificationManagerInternal mNotificationManagerInternal;
|
||||
|
||||
void setPolicy(T policy) {
|
||||
mAppStatePolicy = policy;
|
||||
@@ -201,6 +203,8 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
|
||||
mAppOpsManager = context.getSystemService(AppOpsManager.class);
|
||||
mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
|
||||
mRoleManager = context.getSystemService(RoleManager.class);
|
||||
mNotificationManagerInternal = LocalServices.getService(
|
||||
NotificationManagerInternal.class);
|
||||
|
||||
getPolicy().onSystemReady();
|
||||
}
|
||||
@@ -259,5 +263,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
|
||||
RoleManager getRoleManager() {
|
||||
return mRoleManager;
|
||||
}
|
||||
|
||||
NotificationManagerInternal getNotificationManagerInternal() {
|
||||
return mNotificationManagerInternal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1093,6 +1093,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
|
||||
userId);
|
||||
|
||||
foregroundNoti = localForegroundNoti; // save it for amending next time
|
||||
|
||||
signalForegroundServiceNotification(packageName, appInfo.uid,
|
||||
localForegroundId);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
Slog.w(TAG, "Error showing notification for service", e);
|
||||
// If it gave us a garbage notification, it doesn't
|
||||
@@ -1126,10 +1130,21 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
|
||||
} catch (RuntimeException e) {
|
||||
Slog.w(TAG, "Error canceling notification for service", e);
|
||||
}
|
||||
signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void signalForegroundServiceNotification(String packageName, int uid,
|
||||
int foregroundId) {
|
||||
synchronized (ams) {
|
||||
for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
|
||||
ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
|
||||
packageName, appInfo.uid, foregroundId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stripForegroundServiceFlagFromNotification() {
|
||||
final int localForegroundId = foregroundId;
|
||||
final int localUserId = userId;
|
||||
|
||||
@@ -127,6 +127,7 @@ import com.android.server.am.AppRestrictionController.NotificationHelper;
|
||||
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
|
||||
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
|
||||
import com.android.server.apphibernation.AppHibernationManagerInternal;
|
||||
import com.android.server.notification.NotificationManagerInternal;
|
||||
import com.android.server.pm.UserManagerInternal;
|
||||
import com.android.server.pm.permission.PermissionManagerServiceInternal;
|
||||
import com.android.server.usage.AppStandbyInternal;
|
||||
@@ -215,6 +216,7 @@ public final class BackgroundRestrictionTest {
|
||||
@Mock private PackageManager mPackageManager;
|
||||
@Mock private PackageManagerInternal mPackageManagerInternal;
|
||||
@Mock private NotificationManager mNotificationManager;
|
||||
@Mock private NotificationManagerInternal mNotificationManagerInternal;
|
||||
@Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
|
||||
@Mock private MediaSessionManager mMediaSessionManager;
|
||||
@Mock private RoleManager mRoleManager;
|
||||
@@ -536,11 +538,14 @@ public final class BackgroundRestrictionTest {
|
||||
final float bgRestrictedThreshold = 4.0f;
|
||||
final float bgRestrictedThresholdMah =
|
||||
BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
|
||||
final int testPid = 1234;
|
||||
final int notificationId = 1000;
|
||||
|
||||
DeviceConfigSession<Boolean> bgCurrentDrainMonitor = null;
|
||||
DeviceConfigSession<Long> bgCurrentDrainWindow = null;
|
||||
DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
|
||||
DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
|
||||
DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null;
|
||||
|
||||
mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
|
||||
|
||||
@@ -587,10 +592,24 @@ public final class BackgroundRestrictionTest {
|
||||
isLowRamDeviceStatic() ? 1 : 0]);
|
||||
bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
|
||||
|
||||
bgPromptFgsWithNotiToBgRestricted = new DeviceConfigSession<>(
|
||||
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
|
||||
ConstantsObserver.KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED,
|
||||
DeviceConfig::getBoolean,
|
||||
mContext.getResources().getBoolean(
|
||||
R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted));
|
||||
bgPromptFgsWithNotiToBgRestricted.set(true);
|
||||
|
||||
mCurrentTimeMillis = 10_000L;
|
||||
doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
|
||||
doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp();
|
||||
doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
|
||||
doReturn(true).when(mNotificationManagerInternal).isNotificationShown(
|
||||
testPkgName, null, notificationId, testUser);
|
||||
mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
|
||||
testPid, true);
|
||||
mAppFGSTracker.onForegroundServiceNotificationUpdated(
|
||||
testPkgName, testUid, notificationId);
|
||||
|
||||
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
|
||||
new double[]{restrictBucketThresholdMah - 1, 0},
|
||||
@@ -721,6 +740,85 @@ public final class BackgroundRestrictionTest {
|
||||
Thread.sleep(windowMs);
|
||||
clearInvocations(mInjector.getAppStandbyInternal());
|
||||
clearInvocations(mBgRestrictionController);
|
||||
|
||||
// We're not going to prompt the user if the abusive app has a FGS with notification.
|
||||
bgPromptFgsWithNotiToBgRestricted.set(false);
|
||||
|
||||
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
|
||||
new double[]{bgRestrictedThresholdMah + 1, 0},
|
||||
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
|
||||
() -> {
|
||||
doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
|
||||
doReturn(mCurrentTimeMillis + windowMs)
|
||||
.when(stats).getStatsEndTimestamp();
|
||||
mCurrentTimeMillis += windowMs + 1;
|
||||
// We won't change restriction level automatically because it needs
|
||||
// user consent.
|
||||
try {
|
||||
listener.verify(timeout, testUid, testPkgName,
|
||||
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
|
||||
fail("There shouldn't be level change event like this");
|
||||
} catch (Exception e) {
|
||||
// Expected.
|
||||
}
|
||||
verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
|
||||
eq(testPkgName),
|
||||
eq(STANDBY_BUCKET_RARE),
|
||||
eq(testUser),
|
||||
anyInt(), anyInt());
|
||||
// We should have requested to goto background restricted level.
|
||||
verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
|
||||
eq(testPkgName),
|
||||
eq(testUid));
|
||||
// However, we won't have the prompt to user posted because the policy
|
||||
// is not to show that for FGS with notification.
|
||||
checkNotificationShown(new String[] {testPkgName}, never(), false);
|
||||
});
|
||||
|
||||
// Pretend we have the notification dismissed.
|
||||
mAppFGSTracker.onForegroundServiceNotificationUpdated(
|
||||
testPkgName, testUid, -notificationId);
|
||||
clearInvocations(mInjector.getAppStandbyInternal());
|
||||
clearInvocations(mBgRestrictionController);
|
||||
|
||||
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
|
||||
new double[]{bgRestrictedThresholdMah + 1, 0},
|
||||
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
|
||||
() -> {
|
||||
doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
|
||||
doReturn(mCurrentTimeMillis + windowMs)
|
||||
.when(stats).getStatsEndTimestamp();
|
||||
mCurrentTimeMillis += windowMs + 1;
|
||||
// We won't change restriction level automatically because it needs
|
||||
// user consent.
|
||||
try {
|
||||
listener.verify(timeout, testUid, testPkgName,
|
||||
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
|
||||
fail("There shouldn't be level change event like this");
|
||||
} catch (Exception e) {
|
||||
// Expected.
|
||||
}
|
||||
verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
|
||||
eq(testPkgName),
|
||||
eq(STANDBY_BUCKET_RARE),
|
||||
eq(testUser),
|
||||
anyInt(), anyInt());
|
||||
// We should have requested to goto background restricted level.
|
||||
verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
|
||||
eq(testPkgName),
|
||||
eq(testUid));
|
||||
// Verify we have the notification posted now because its FGS is invisible.
|
||||
checkNotificationShown(new String[] {testPkgName}, atLeast(1), true);
|
||||
});
|
||||
|
||||
// Pretend notification is back on.
|
||||
mAppFGSTracker.onForegroundServiceNotificationUpdated(
|
||||
testPkgName, testUid, notificationId);
|
||||
// Now we'll prompt the user even it has a FGS with notification.
|
||||
bgPromptFgsWithNotiToBgRestricted.set(true);
|
||||
clearInvocations(mInjector.getAppStandbyInternal());
|
||||
clearInvocations(mBgRestrictionController);
|
||||
|
||||
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
|
||||
new double[]{bgRestrictedThresholdMah + 1, 0},
|
||||
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
|
||||
@@ -785,6 +883,7 @@ public final class BackgroundRestrictionTest {
|
||||
closeIfNotNull(bgCurrentDrainWindow);
|
||||
closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
|
||||
closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
|
||||
closeIfNotNull(bgPromptFgsWithNotiToBgRestricted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2460,6 +2559,11 @@ public final class BackgroundRestrictionTest {
|
||||
return BackgroundRestrictionTest.this.mMediaSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
NotificationManagerInternal getNotificationManagerInternal() {
|
||||
return BackgroundRestrictionTest.this.mNotificationManagerInternal;
|
||||
}
|
||||
|
||||
@Override
|
||||
long getServiceStartForegroundTimeout() {
|
||||
return 1_000; // ms
|
||||
|
||||
Reference in New Issue
Block a user