Merge "Merge "Add support to persist app background restriction settings." into tm-dev am: 4935cbc65a"
This commit is contained in:
committed by
Android (Google) Code Review
commit
e90adf37a3
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user