Add support to persist app background restriction settings.

Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest
Bug: 203105544
Bug: 225951652
Change-Id: I3825e70fa79b54684c4e2112e04214757d0265b5
This commit is contained in:
Jing Ji
2022-03-23 11:25:17 -07:00
parent a5fdb746cf
commit 467cd469bd
2 changed files with 559 additions and 28 deletions

View File

@@ -80,7 +80,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA
import static com.android.server.am.AppFGSTracker.foregroundServiceTypeToIndex;
import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -131,11 +131,16 @@ import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.TimeUtils;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -152,6 +157,15 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -163,6 +177,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
@@ -192,6 +207,16 @@ public final class AppRestrictionController {
*/
private static final boolean ENABLE_SHOW_FGS_MANAGER_ACTION_ON_BG_RESTRICTION = false;
private static final String APP_RESTRICTION_SETTINGS_DIRNAME = "apprestriction";
private static final String APP_RESTRICTION_SETTINGS_FILENAME = "settings.xml";
private static final String TAG_SETTINGS = "settings";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_UID = "uid";
private static final String ATTR_CUR_LEVEL = "curlevel";
private static final String ATTR_LEVEL_TS = "levelts";
private static final String ATTR_REASON = "reason";
private final Context mContext;
private final HandlerThread mBgHandlerThread;
private final BgHandler mBgHandler;
@@ -200,8 +225,9 @@ public final class AppRestrictionController {
// No lock is needed, as it's immutable after initialization in constructor.
private final ArrayList<BaseAppStateTracker> mAppStateTrackers = new ArrayList<>();
@VisibleForTesting
@GuardedBy("mSettingsLock")
private final RestrictionSettings mRestrictionSettings = new RestrictionSettings();
final RestrictionSettings mRestrictionSettings = new RestrictionSettings();
private final CopyOnWriteArraySet<AppBackgroundRestrictionListener> mRestrictionListeners =
new CopyOnWriteArraySet<>();
@@ -264,6 +290,11 @@ public final class AppRestrictionController {
@GuardedBy("mCarrierPrivilegedLock")
private List<String> mCarrierPrivilegedApps;
/**
* Whether or not we've loaded the restriction settings from the persistent storage.
*/
private final AtomicBoolean mRestrictionSettingsXmlLoaded = new AtomicBoolean();
final ActivityManagerService mActivityManagerService;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -363,10 +394,10 @@ public final class AppRestrictionController {
private @RestrictionLevel int mCurrentRestrictionLevel;
private @RestrictionLevel int mLastRestrictionLevel;
private @ElapsedRealtimeLong long mLevelChangeTimeElapsed;
private @CurrentTimeMillisLong long mLevelChangeTime;
private int mReason;
private @ElapsedRealtimeLong long[] mLastNotificationShownTimeElapsed;
private @CurrentTimeMillisLong long[] mLastNotificationShownTime;
private int[] mNotificationId;
PkgSettings(String packageName, int uid) {
@@ -380,7 +411,7 @@ public final class AppRestrictionController {
if (level != mCurrentRestrictionLevel) {
mLastRestrictionLevel = mCurrentRestrictionLevel;
mCurrentRestrictionLevel = level;
mLevelChangeTimeElapsed = SystemClock.elapsedRealtime();
mLevelChangeTime = mInjector.currentTimeMillis();
mReason = (REASON_MAIN_MASK & reason) | (REASON_SUB_MASK & subReason);
mBgHandler.obtainMessage(BgHandler.MSG_APP_RESTRICTION_LEVEL_CHANGED,
mUid, level, mPackageName).sendToTarget();
@@ -407,7 +438,7 @@ public final class AppRestrictionController {
return sb.toString();
}
void dump(PrintWriter pw, @ElapsedRealtimeLong long nowElapsed) {
void dump(PrintWriter pw, @CurrentTimeMillisLong long now) {
synchronized (mSettingsLock) {
pw.print(toString());
if (mLastRestrictionLevel != RESTRICTION_LEVEL_UNKNOWN) {
@@ -415,15 +446,14 @@ public final class AppRestrictionController {
pw.print(ActivityManager.restrictionLevelToName(mLastRestrictionLevel));
}
pw.print(" levelChange=");
TimeUtils.formatDuration(mLevelChangeTimeElapsed - nowElapsed, pw);
if (mLastNotificationShownTimeElapsed != null) {
for (int i = 0; i < mLastNotificationShownTimeElapsed.length; i++) {
if (mLastNotificationShownTimeElapsed[i] > 0) {
TimeUtils.formatDuration(mLevelChangeTime - now, pw);
if (mLastNotificationShownTime != null) {
for (int i = 0; i < mLastNotificationShownTime.length; i++) {
if (mLastNotificationShownTime[i] > 0) {
pw.print(" lastNoti(");
pw.print(mNotificationHelper.notificationTypeToString(i));
pw.print(")=");
TimeUtils.formatDuration(
mLastNotificationShownTimeElapsed[i] - nowElapsed, pw);
TimeUtils.formatDuration(mLastNotificationShownTime[i] - now, pw);
}
}
}
@@ -456,22 +486,32 @@ public final class AppRestrictionController {
}
@GuardedBy("mSettingsLock")
@ElapsedRealtimeLong long getLastNotificationTime(
@CurrentTimeMillisLong long getLastNotificationTime(
@NotificationHelper.NotificationType int notificationType) {
if (mLastNotificationShownTimeElapsed == null) {
if (mLastNotificationShownTime == null) {
return 0;
}
return mLastNotificationShownTimeElapsed[notificationType];
return mLastNotificationShownTime[notificationType];
}
@GuardedBy("mSettingsLock")
void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
@ElapsedRealtimeLong long timestamp) {
if (mLastNotificationShownTimeElapsed == null) {
mLastNotificationShownTimeElapsed =
@CurrentTimeMillisLong long timestamp) {
setLastNotificationTime(notificationType, timestamp, true);
}
@VisibleForTesting
@GuardedBy("mSettingsLock")
void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
@CurrentTimeMillisLong long timestamp, boolean persist) {
if (mLastNotificationShownTime == null) {
mLastNotificationShownTime =
new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
}
mLastNotificationShownTimeElapsed[notificationType] = timestamp;
mLastNotificationShownTime[notificationType] = timestamp;
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(mUid));
}
}
@GuardedBy("mSettingsLock")
@@ -490,6 +530,51 @@ public final class AppRestrictionController {
}
mNotificationId[notificationType] = notificationId;
}
@VisibleForTesting
@GuardedBy("mSettingsLock")
void setLevelChangeTime(@CurrentTimeMillisLong long timestamp) {
mLevelChangeTime = timestamp;
}
@GuardedBy("mSettingsLock")
@Override
public Object clone() {
final PkgSettings newObj = new PkgSettings(mPackageName, mUid);
newObj.mCurrentRestrictionLevel = mCurrentRestrictionLevel;
newObj.mLastRestrictionLevel = mLastRestrictionLevel;
newObj.mLevelChangeTime = mLevelChangeTime;
newObj.mReason = mReason;
if (mLastNotificationShownTime != null) {
newObj.mLastNotificationShownTime = Arrays.copyOf(
mLastNotificationShownTime, mLastNotificationShownTime.length);
}
if (mNotificationId != null) {
newObj.mNotificationId = Arrays.copyOf(mNotificationId, mNotificationId.length);
}
return newObj;
}
@GuardedBy("mSettingsLock")
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || !(other instanceof PkgSettings)) {
return false;
}
final PkgSettings otherSettings = (PkgSettings) other;
return otherSettings.mUid == mUid
&& otherSettings.mCurrentRestrictionLevel == mCurrentRestrictionLevel
&& otherSettings.mLastRestrictionLevel == mLastRestrictionLevel
&& otherSettings.mLevelChangeTime == mLevelChangeTime
&& otherSettings.mReason == mReason
&& TextUtils.equals(otherSettings.mPackageName, mPackageName)
&& Arrays.equals(otherSettings.mLastNotificationShownTime,
mLastNotificationShownTime)
&& Arrays.equals(otherSettings.mNotificationId, mNotificationId);
}
}
/**
@@ -604,21 +689,58 @@ public final class AppRestrictionController {
}
void removePackage(String pkgName, int uid) {
removePackage(pkgName, uid, true);
}
void removePackage(String pkgName, int uid, boolean persist) {
synchronized (mSettingsLock) {
final int keyIndex = mRestrictionLevels.indexOfKey(uid);
mRestrictionLevels.delete(uid, pkgName);
if (keyIndex >= 0 && mRestrictionLevels.numElementsForKeyAt(keyIndex) == 0) {
mRestrictionLevels.deleteAt(keyIndex);
}
}
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(uid));
}
}
void removeUid(int uid) {
removeUid(uid, true);
}
void removeUid(int uid, boolean persist) {
synchronized (mSettingsLock) {
mRestrictionLevels.delete(uid);
}
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(uid));
}
}
@VisibleForTesting
void reset() {
synchronized (mSettingsLock) {
mRestrictionLevels.clear();
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
mRestrictionLevels.deleteAt(i);
}
}
}
@VisibleForTesting
void resetToDefault() {
synchronized (mSettingsLock) {
mRestrictionLevels.forEach(settings -> {
settings.mCurrentRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
settings.mLastRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
settings.mLevelChangeTime = 0L;
settings.mReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED;
if (settings.mLastNotificationShownTime != null) {
for (int i = 0; i < settings.mLastNotificationShownTime.length; i++) {
settings.mLastNotificationShownTime[i] = 0L;
}
}
});
}
}
@@ -628,16 +750,272 @@ public final class AppRestrictionController {
mRestrictionLevels.forEach(setting -> settings.add(setting));
}
Collections.sort(settings, Comparator.comparingInt(PkgSettings::getUid));
final long nowElapsed = SystemClock.elapsedRealtime();
final long now = mInjector.currentTimeMillis();
for (int i = 0, size = settings.size(); i < size; i++) {
pw.print(prefix);
pw.print('#');
pw.print(i);
pw.print(' ');
settings.get(i).dump(pw, nowElapsed);
settings.get(i).dump(pw, now);
pw.println();
}
}
@VisibleForTesting
void schedulePersistToXml(@UserIdInt int userId) {
mBgHandler.obtainMessage(BgHandler.MSG_PERSIST_RESTRICTION_SETTINGS, userId, 0)
.sendToTarget();
}
@VisibleForTesting
void scheduleLoadFromXml() {
mBgHandler.sendEmptyMessage(BgHandler.MSG_LOAD_RESTRICTION_SETTINGS);
}
@VisibleForTesting
File getXmlFileNameForUser(@UserIdInt int userId) {
final File dir = new File(mInjector.getDataSystemDeDirectory(
userId), APP_RESTRICTION_SETTINGS_DIRNAME);
return new File(dir, APP_RESTRICTION_SETTINGS_FILENAME);
}
@VisibleForTesting
void loadFromXml(boolean applyLevel) {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (int userId : allUsers) {
loadFromXml(userId, applyLevel);
}
mRestrictionSettingsXmlLoaded.set(true);
}
void loadFromXml(@UserIdInt int userId, boolean applyLevel) {
final File file = getXmlFileNameForUser(userId);
if (!file.exists()) {
return;
}
final long[] ts = new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
try (InputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
final long now = SystemClock.elapsedRealtime();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String tagName = parser.getName();
if (!TAG_SETTINGS.equals(tagName)) {
Slog.w(TAG, "Unexpected tag name: " + tagName);
continue;
}
loadOneFromXml(parser, now, ts, applyLevel);
}
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Loaded from " + file);
}
} catch (IOException | XmlPullParserException e) {
}
}
private void loadOneFromXml(TypedXmlPullParser parser, long now, long[] ts,
boolean applyLevel) {
// Reset the buffer.
for (int i = 0; i < ts.length; i++) {
ts[i] = 0L;
}
// Walk through the attributes.
int uid = 0;
String packageName = null;
int curLevel = RESTRICTION_LEVEL_UNKNOWN;
int reason = REASON_MAIN_DEFAULT;
long levelTs = 0L;
for (int i = 0; i < parser.getAttributeCount(); i++) {
try {
final String attrName = parser.getAttributeName(i);
final String attrValue = parser.getAttributeValue(i);
switch (attrName) {
case ATTR_UID:
uid = Integer.parseInt(attrValue);
break;
case ATTR_PACKAGE:
packageName = attrValue;
break;
case ATTR_CUR_LEVEL:
curLevel = Integer.parseInt(attrValue);
break;
case ATTR_LEVEL_TS:
levelTs = Long.parseLong(attrValue);
break;
case ATTR_REASON:
reason = Integer.parseInt(attrValue);
break;
default:
@NotificationHelper.NotificationType int type =
NotificationHelper.notificationTimeAttrToType(attrName);
ts[type] = Long.parseLong(attrValue);
break;
}
} catch (IllegalArgumentException e) {
}
}
if (uid != 0) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Restoring " + packageName + "/" + UserHandle.formatUid(uid)
+ " level=" + curLevel + " reason=" + Integer.toHexString(reason)
+ " ts=" + levelTs + " noti=" + Arrays.toString(ts));
}
final PkgSettings pkgSettings;
synchronized (mSettingsLock) {
pkgSettings = getRestrictionSettingsLocked(uid, packageName);
if (pkgSettings == null) {
return;
}
for (int i = 0; i < ts.length; i++) {
if (pkgSettings.getLastNotificationTime(i) == 0 && ts[i] != 0) {
pkgSettings.setLastNotificationTime(i, ts[i], false);
}
}
if (pkgSettings.mCurrentRestrictionLevel >= curLevel) {
// The current restriction level is the same or more restrictive,
// don't restore.
return;
}
}
final int curBucket = mInjector.getAppStandbyInternal().getAppStandbyBucket(
packageName, UserHandle.getUserId(uid), now, false);
if (applyLevel) {
applyRestrictionLevel(packageName, uid, curLevel, curBucket, true,
reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
} else {
pkgSettings.update(curLevel,
reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
}
synchronized (mSettingsLock) {
// Restore the mLevelChangeTime too.
pkgSettings.setLevelChangeTime(levelTs);
}
}
}
@VisibleForTesting
void persistToXml(@UserIdInt int userId) {
final File file = getXmlFileNameForUser(userId);
final File dir = file.getParentFile();
if (!dir.isDirectory() && !dir.mkdirs()) {
Slog.w(TAG, "Failed to create folder for " + userId);
return;
}
final AtomicFile atomicFile = new AtomicFile(file);
FileOutputStream stream = null;
try {
stream = atomicFile.startWrite();
stream.write(toXmlByteArray(userId));
} catch (Exception e) {
Slog.e(TAG, "Failed to write file " + file, e);
if (stream != null) {
atomicFile.failWrite(stream);
}
return;
}
atomicFile.finishWrite(stream);
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Successfully written to " + atomicFile);
}
}
private byte[] toXmlByteArray(@UserIdInt int userId) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
final TypedXmlSerializer serializer = Xml.resolveSerializer(os);
serializer.startDocument(/* encoding */ null, /* standalone */ true);
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
final int uid = settings.getUid();
if (UserHandle.getUserId(uid) != userId) {
continue;
}
serializer.startTag(null, TAG_SETTINGS);
serializer.attributeInt(null, ATTR_UID, uid);
serializer.attribute(null, ATTR_PACKAGE, settings.getPackageName());
serializer.attributeInt(null, ATTR_CUR_LEVEL,
settings.mCurrentRestrictionLevel);
serializer.attributeLong(null, ATTR_LEVEL_TS,
settings.mLevelChangeTime);
serializer.attributeInt(null, ATTR_REASON, settings.mReason);
for (int k = 0; k < NotificationHelper.NOTIFICATION_TYPE_LAST; k++) {
serializer.attributeLong(null,
NotificationHelper.notificationTypeToTimeAttr(k),
settings.getLastNotificationTime(k));
}
serializer.endTag(null, TAG_SETTINGS);
}
}
}
serializer.endDocument();
serializer.flush();
return os.toByteArray();
} catch (IOException e) {
return null;
}
}
@VisibleForTesting
void removeXml() {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (int userId : allUsers) {
getXmlFileNameForUser(userId).delete();
}
}
@Override
public Object clone() {
final RestrictionSettings newObj = new RestrictionSettings();
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
newObj.mRestrictionLevels.add(mRestrictionLevels.keyAt(i),
mRestrictionLevels.keyAt(i, j), (PkgSettings) settings.clone());
}
}
}
return newObj;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || !(other instanceof RestrictionSettings)) {
return false;
}
final SparseArrayMap<String, PkgSettings> otherSettings = ((RestrictionSettings) other)
.mRestrictionLevels;
synchronized (mSettingsLock) {
if (otherSettings.numMaps() != mRestrictionLevels.numMaps()) {
return false;
}
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
final int uid = mRestrictionLevels.keyAt(i);
if (otherSettings.numElementsForKey(uid)
!= mRestrictionLevels.numElementsForKeyAt(i)) {
return false;
}
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
if (!settings.equals(otherSettings.get(uid, settings.getPackageName()))) {
return false;
}
}
}
}
return true;
}
}
final class ConstantsObserver extends ContentObserver implements
@@ -1010,6 +1388,7 @@ public final class AppRestrictionController {
DeviceConfig.removeOnPropertiesChangedListener(mConstantsObserver);
unregisterForUidObservers();
unregisterForSystemBroadcasts();
mRestrictionSettings.removeXml();
}
private void initBgRestrictionExemptioFromSysConfig() {
@@ -1034,6 +1413,12 @@ public final class AppRestrictionController {
refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
REASON_SUB_FORCED_USER_FLAG_INTERACTION);
}
// Load the previously saved levels and update the current levels if needed.
mRestrictionSettings.scheduleLoadFromXml();
// Also save the current levels right away.
for (int userId : allUsers) {
mRestrictionSettings.schedulePersistToXml(userId);
}
}
private void initSystemModuleNames() {
@@ -1642,6 +2027,9 @@ public final class AppRestrictionController {
static final int NOTIFICATION_TYPE_LONG_RUNNING_FGS = 1;
static final int NOTIFICATION_TYPE_LAST = 2;
static final String ATTR_LAST_BATTERY_NOTIFICATION_TIME = "last_batt_noti_ts";
static final String ATTR_LAST_LONG_FGS_NOTIFICATION_TIME = "last_long_fgs_noti_ts";
@IntDef(prefix = { "NOTIFICATION_TYPE_"}, value = {
NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
NOTIFICATION_TYPE_LONG_RUNNING_FGS,
@@ -1654,6 +2042,25 @@ public final class AppRestrictionController {
"Long-running FGS",
};
static final String[] NOTIFICATION_TIME_ATTRS = {
ATTR_LAST_BATTERY_NOTIFICATION_TIME,
ATTR_LAST_LONG_FGS_NOTIFICATION_TIME,
};
static @NotificationType int notificationTimeAttrToType(@NonNull String attr) {
switch (attr) {
case ATTR_LAST_BATTERY_NOTIFICATION_TIME:
return NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN;
case ATTR_LAST_LONG_FGS_NOTIFICATION_TIME:
return NOTIFICATION_TYPE_LONG_RUNNING_FGS;
}
throw new IllegalArgumentException();
}
static @NonNull String notificationTypeToTimeAttr(@NotificationType int type) {
return NOTIFICATION_TIME_ATTRS[type];
}
static final String ACTION_FGS_MANAGER_TRAMPOLINE =
"com.android.server.am.ACTION_FGS_MANAGER_TRAMPOLINE";
@@ -1801,14 +2208,14 @@ public final class AppRestrictionController {
return 0;
}
final long now = SystemClock.elapsedRealtime();
final long lastNotificationShownTimeElapsed =
final long now = mInjector.currentTimeMillis();
final long lastNotificationShownTime =
settings.getLastNotificationTime(notificationType);
if (lastNotificationShownTimeElapsed != 0 && (lastNotificationShownTimeElapsed
if (lastNotificationShownTime != 0 && (lastNotificationShownTime
+ getNotificationMinInterval(notificationType) > now)) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Not showing notification as last notification was shown "
+ TimeUtils.formatDuration(now - lastNotificationShownTimeElapsed)
+ TimeUtils.formatDuration(now - lastNotificationShownTime)
+ " ago");
}
return 0;
@@ -1824,7 +2231,7 @@ public final class AppRestrictionController {
+ "/" + UserHandle.formatUid(uid)
+ ", id=" + notificationId
+ ", now=" + now
+ ", lastShown=" + lastNotificationShownTimeElapsed);
+ ", lastShown=" + lastNotificationShownTime);
}
return notificationId;
}
@@ -1861,7 +2268,7 @@ public final class AppRestrictionController {
ABUSIVE_BACKGROUND_APPS)
.setAutoCancel(true)
.setGroup(GROUP_KEY)
.setWhen(System.currentTimeMillis())
.setWhen(mInjector.currentTimeMillis())
.setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
@@ -2181,6 +2588,8 @@ public final class AppRestrictionController {
static final int MSG_UID_GONE = 7;
static final int MSG_UID_PROC_STATE_CHANGED = 8;
static final int MSG_CANCEL_REQUEST_BG_RESTRICTED = 9;
static final int MSG_LOAD_RESTRICTION_SETTINGS = 10;
static final int MSG_PERSIST_RESTRICTION_SETTINGS = 11;
private final Injector mInjector;
@@ -2226,6 +2635,12 @@ public final class AppRestrictionController {
c.handleUidInactive(msg.arg1, msg.arg2 == 1);
c.handleUidGone(msg.arg1);
} break;
case MSG_LOAD_RESTRICTION_SETTINGS: {
c.mRestrictionSettings.loadFromXml(true);
} break;
case MSG_PERSIST_RESTRICTION_SETTINGS: {
c.mRestrictionSettings.persistToXml(msg.arg1);
} break;
}
}
}
@@ -2399,6 +2814,14 @@ public final class AppRestrictionController {
void scheduleInitTrackers(Handler handler, Runnable initializers) {
handler.post(initializers);
}
File getDataSystemDeDirectory(@UserIdInt int userId) {
return Environment.getDataSystemDeDirectory(userId);
}
@CurrentTimeMillisLong long currentTimeMillis() {
return System.currentTimeMillis();
}
}
private void registerForSystemBroadcasts() {

View File

@@ -36,11 +36,15 @@ import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
@@ -93,6 +97,7 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.ActivityManagerInternal.BindServiceEventListener;
@@ -146,6 +151,7 @@ import com.android.server.am.AppFGSTracker.AppFGSPolicy;
import com.android.server.am.AppMediaSessionTracker.AppMediaSessionPolicy;
import com.android.server.am.AppRestrictionController.ConstantsObserver;
import com.android.server.am.AppRestrictionController.NotificationHelper;
import com.android.server.am.AppRestrictionController.RestrictionSettings;
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
import com.android.server.apphibernation.AppHibernationManagerInternal;
@@ -165,6 +171,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -356,6 +363,7 @@ public final class BackgroundRestrictionTest {
verify(mAppBindServiceEventsTracker.mInjector.getActivityManagerInternal())
.addBindServiceEventListener(mBindServiceEventListenerCap.capture());
mBindServiceEventListener = mBindServiceEventListenerCap.getValue();
waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
}
@After
@@ -2788,6 +2796,96 @@ public final class BackgroundRestrictionTest {
new double[] {10.0d, 1.0d, 12.0d, 1.0d, 18.0d, 1.0d})));
}
@SuppressWarnings("GuardedBy")
@Test
public void testPersistRestrictionSettings() throws Exception {
final RestrictionSettings settings = mBgRestrictionController.mRestrictionSettings;
final String testPkg0 = TEST_PACKAGE_BASE + 0;
final String testPkg1 = TEST_PACKAGE_BASE + 1;
final String testPkg2 = TEST_PACKAGE_BASE + 2;
final String testPkg3 = TEST_PACKAGE_BASE + 3;
final int testUid0 = UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 0);
final int testUid1 = UserHandle.getUid(TEST_USER0, TEST_PACKAGE_APPID_BASE + 1);
final int testUid2 = UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 2);
final int testUid3 = UserHandle.getUid(TEST_USER1, TEST_PACKAGE_APPID_BASE + 3);
settings.reset();
setRestrictionSettings(settings, testPkg0, testUid0,
RESTRICTION_LEVEL_ADAPTIVE_BUCKET,
10_000L, REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED,
new long[] {0L, 0L}, false);
setRestrictionSettings(settings, testPkg1, testUid1,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
20_000L, REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE,
new long[] {10_000L, 0L}, false);
setRestrictionSettings(settings, testPkg2, testUid2,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
25_000L, REASON_MAIN_FORCED_BY_USER | REASON_SUB_FORCED_USER_FLAG_INTERACTION,
new long[] {0L, 15_000L}, false);
setRestrictionSettings(settings, testPkg3, testUid3,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
30_000L, REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED,
new long[] {0L, 0L}, false);
RestrictionSettings test = (RestrictionSettings) settings.clone();
// Verify our clone works correctly.
assertTrue(settings.equals(test));
// Reset the test object.
test.resetToDefault();
// Save the original data into xml.
settings.persistToXml(TEST_USER0);
settings.persistToXml(TEST_USER1);
// Load it to our test object.
test.loadFromXml(false);
// Verify we restored it correctly.
assertTrue(settings.equals(test));
// Remove one package.
settings.removePackage(testPkg3, testUid3);
// Verify it.
verifyLoadedSettings(settings);
// Add it back.
setRestrictionSettings(settings, testPkg3, testUid3,
RESTRICTION_LEVEL_RESTRICTED_BUCKET,
30_000L, REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED,
new long[] {0L, 1_000L}, true);
// Verify it.
verifyLoadedSettings(settings);
// Remove one user.
settings.removeUser(TEST_USER1);
// Verify it.
verifyLoadedSettings(settings);
}
private void verifyLoadedSettings(RestrictionSettings settings) throws Exception {
// Make a new copy and reset it.
RestrictionSettings test = (RestrictionSettings) settings.clone();
test.resetToDefault();
// Wait for the idleness so the data is persisted.
waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
// Load it to our test object.
test.loadFromXml(false);
// Verify we restored it correctly.
assertTrue(settings.equals(test));
}
@SuppressWarnings("GuardedBy")
private void setRestrictionSettings(RestrictionSettings settings, String pkgName, int uid,
int level, long levelTs, int reason, long[] notificationTime, boolean persist) {
mCurrentTimeMillis = levelTs;
settings.update(pkgName, uid, level, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
final RestrictionSettings.PkgSettings pkgSettings = settings.getRestrictionSettingsLocked(
uid, pkgName);
for (int i = 0; i < notificationTime.length; i++) {
pkgSettings.setLastNotificationTime(i, notificationTime[i], persist);
}
}
private LinkedList<UidStateEventWithBattery> createUidStateEventWithBatteryList(
boolean[] isStart, long[] timestamps, double[] batteryUsage) {
final LinkedList<UidStateEventWithBattery> result = new LinkedList<>();
@@ -2948,6 +3046,16 @@ public final class BackgroundRestrictionTest {
void scheduleInitTrackers(Handler handler, Runnable initializers) {
initializers.run();
}
@Override
File getDataSystemDeDirectory(@UserIdInt int userId) {
return new File(mContext.getFilesDir(), Integer.toString(userId));
}
@Override
long currentTimeMillis() {
return mCurrentTimeMillis;
}
}
private class TestBaseTrackerInjector<T extends BaseAppStatePolicy>