diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 910ca8e0974cf..35b7926a2ae4b 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -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 mAppStateTrackers = new ArrayList<>(); + @VisibleForTesting @GuardedBy("mSettingsLock") - private final RestrictionSettings mRestrictionSettings = new RestrictionSettings(); + final RestrictionSettings mRestrictionSettings = new RestrictionSettings(); private final CopyOnWriteArraySet mRestrictionListeners = new CopyOnWriteArraySet<>(); @@ -264,6 +290,11 @@ public final class AppRestrictionController { @GuardedBy("mCarrierPrivilegedLock") private List 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 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() { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 47b715ebd7c09..b512ac51e2fb7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -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 createUidStateEventWithBatteryList( boolean[] isStart, long[] timestamps, double[] batteryUsage) { final LinkedList 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