From 89b2cfa35c3559b0a78beafcbd90da0157534d08 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 19 Sep 2022 23:18:23 +0000 Subject: [PATCH] Increase app updater balances. Increase the minimum balances of app updaters in proportion to the number of apps they're responsible for updating so that it's easier for them to do app updates in the background. Bug: 243987091 Test: Manually check balances are adjusted in dump Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/tare Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/tare Change-Id: I0d431f2308b8e4938437d3141318306f96296732 --- .../java/android/app/tare/EconomyManager.java | 12 +++ .../server/tare/InternalResourceService.java | 86 ++++++++++++++++++- .../tare/JobSchedulerEconomicPolicy.java | 24 +++++- .../java/com/android/server/tare/Ledger.java | 2 +- .../tare/JobSchedulerEconomicPolicyTest.java | 45 ++++++++-- 5 files changed, 154 insertions(+), 15 deletions(-) diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index be0e02595b60b..6af24be3a3725 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -227,6 +227,9 @@ public class EconomyManager { public static final String KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP = "js_min_satiated_balance_other_app"; /** @hide */ + public static final String KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER = + "js_min_satiated_balance_increment_updater"; + /** @hide */ public static final String KEY_JS_MAX_SATIATED_BALANCE = "js_max_satiated_balance"; /** @hide */ @@ -509,6 +512,15 @@ public class EconomyManager { public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0); /** @hide */ public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000); + /** + * How many credits to increase the updating app's min satiated balance by for each app that it + * is responsible for updating. + * @hide + */ + public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES = + // Research indicates that the median time between popular app updates is 13-14 days, + // so adjust by 14 to amortize over that time. + DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES / 14; /** @hide */ public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3); /** @hide */ diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index c13e1dd978d6f..4a26d213a48a5 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -81,6 +81,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArraySet; /** @@ -164,6 +165,10 @@ public class InternalResourceService extends SystemService { @GuardedBy("mLock") private final SparseArrayMap mVipOverrides = new SparseArrayMap<>(); + /** Set of apps each installer is responsible for installing. */ + @GuardedBy("mLock") + private final SparseArrayMap> mInstallers = new SparseArrayMap<>(); + private volatile boolean mHasBattery = true; private volatile boolean mIsEnabled; private volatile int mBootPhase; @@ -353,6 +358,14 @@ public class InternalResourceService extends SystemService { return mCompleteEconomicPolicy; } + /** Returns the number of apps that this app is expected to update at some point. */ + int getAppUpdateResponsibilityCount(final int userId, @NonNull final String pkgName) { + synchronized (mLock) { + // TODO(248274798): return 0 if the app has lost the install permission + return ArrayUtils.size(mInstallers.get(userId, pkgName)); + } + } + @NonNull SparseArrayMap getInstalledPackages() { synchronized (mLock) { @@ -525,7 +538,8 @@ public class InternalResourceService extends SystemService { } synchronized (mLock) { final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo); - mPkgCache.add(userId, pkgName, ipo); + final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo); + maybeUpdateInstallerStatusLocked(oldIpo, ipo); mUidToPackageCache.add(uid, pkgName); // TODO: only do this when the user first launches the app (app leaves stopped state) mAgent.grantBirthrightLocked(userId, pkgName); @@ -552,7 +566,14 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { mUidToPackageCache.remove(uid, pkgName); mVipOverrides.delete(userId, pkgName); - mPkgCache.delete(userId, pkgName); + final InstalledPackageInfo ipo = mPkgCache.delete(userId, pkgName); + mInstallers.delete(userId, pkgName); + if (ipo != null && ipo.installerPackageName != null) { + final ArraySet list = mInstallers.get(userId, ipo.installerPackageName); + if (list != null) { + list.remove(pkgName); + } + } mAgent.onPackageRemovedLocked(userId, pkgName); } } @@ -574,7 +595,8 @@ public class InternalResourceService extends SystemService { mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i)); - mPkgCache.add(userId, ipo.packageName, ipo); + final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); + maybeUpdateInstallerStatusLocked(oldIpo, ipo); } mAgent.grantBirthrightsLocked(userId); } @@ -590,6 +612,7 @@ public class InternalResourceService extends SystemService { mUidToPackageCache.remove(pkgInfo.uid); } } + mInstallers.delete(userId); mPkgCache.delete(userId); mAgent.onUserRemovedLocked(userId); } @@ -746,11 +769,49 @@ public class InternalResourceService extends SystemService { mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i)); - mPkgCache.add(userId, ipo.packageName, ipo); + final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); + maybeUpdateInstallerStatusLocked(oldIpo, ipo); } } } + /** + * Used to update the set of installed apps for each installer. This only has an effect if the + * installer package name is different between {@code oldIpo} and {@code newIpo}. + */ + @GuardedBy("mLock") + private void maybeUpdateInstallerStatusLocked(@Nullable InstalledPackageInfo oldIpo, + @NonNull InstalledPackageInfo newIpo) { + final boolean changed; + if (oldIpo == null) { + changed = newIpo.installerPackageName != null; + } else { + changed = !Objects.equals(oldIpo.installerPackageName, newIpo.installerPackageName); + } + if (!changed) { + return; + } + // InstallSourceInfo doesn't track userId, so for now, assume the installer on the package's + // user profile did the installation. + // TODO(246640162): use the actual installer's user ID + final int userId = UserHandle.getUserId(newIpo.uid); + final String pkgName = newIpo.packageName; + if (oldIpo != null) { + final ArraySet oldList = mInstallers.get(userId, oldIpo.installerPackageName); + if (oldList != null) { + oldList.remove(pkgName); + } + } + if (newIpo.installerPackageName != null) { + ArraySet newList = mInstallers.get(userId, newIpo.installerPackageName); + if (newList == null) { + newList = new ArraySet<>(); + mInstallers.add(userId, newIpo.installerPackageName, newList); + } + newList.add(pkgName); + } + } + private void registerListeners() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); @@ -1359,6 +1420,23 @@ public class InternalResourceService extends SystemService { } pw.println(); + pw.println(); + pw.println("Installers:"); + pw.increaseIndent(); + for (int u = 0; u < mInstallers.numMaps(); ++u) { + final int userId = mInstallers.keyAt(u); + + for (int p = 0; p < mInstallers.numElementsForKeyAt(u); ++p) { + final String pkgName = mInstallers.keyAt(u, p); + + pw.print(appToString(userId, pkgName)); + pw.print(": "); + pw.print(mInstallers.valueAt(u, p).size()); + pw.println(" apps"); + } + } + pw.decreaseIndent(); + pw.println(); mCompleteEconomicPolicy.dump(pw); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 55cc3520961e3..322e824286d50 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -42,6 +42,7 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_ import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES; @@ -87,6 +88,7 @@ import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED; +import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT; import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_MAX; @@ -154,6 +156,7 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedBalanceExempted; private long mMinSatiatedBalanceOther; + private long mMinSatiatedBalanceIncrementalAppUpdater; private long mMaxSatiatedBalance; private long mInitialSatiatedConsumptionLimit; private long mHardSatiatedConsumptionLimit; @@ -183,11 +186,20 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { if (mIrs.isPackageRestricted(userId, pkgName)) { return 0; } + + final long baseBalance; if (mIrs.isPackageExempted(userId, pkgName)) { - return mMinSatiatedBalanceExempted; + baseBalance = mMinSatiatedBalanceExempted; + } else { + baseBalance = mMinSatiatedBalanceOther; } - // TODO: take other exemptions into account - return mMinSatiatedBalanceOther; + + long minBalance = baseBalance; + + final int updateResponsibilityCount = mIrs.getAppUpdateResponsibilityCount(userId, pkgName); + minBalance += updateResponsibilityCount * mMinSatiatedBalanceIncrementalAppUpdater; + + return Math.min(minBalance, mMaxSatiatedBalance); } @Override @@ -242,6 +254,9 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, mMinSatiatedBalanceOther); + mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER, + DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES); mMaxSatiatedBalance = getConstantAsCake(mParser, properties, KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); @@ -397,10 +412,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { @Override void dump(IndentingPrintWriter pw) { - pw.println("Min satiated balances:"); + pw.println("Min satiated balance:"); pw.increaseIndent(); pw.print("Exempted", cakeToString(mMinSatiatedBalanceExempted)).println(); pw.print("Other", cakeToString(mMinSatiatedBalanceOther)).println(); + pw.print("+App Updater", cakeToString(mMinSatiatedBalanceIncrementalAppUpdater)).println(); pw.decreaseIndent(); pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println(); pw.print("Consumption limits: ["); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java index 620d1a0da76f2..a68170c9bac7b 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java @@ -286,7 +286,7 @@ class Ledger { final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS) % NUM_REWARD_BUCKET_WINDOWS; final RewardBucket rewardBucket = mRewardBuckets[idx]; - if (rewardBucket == null) { + if (rewardBucket == null || rewardBucket.startTimeMs == 0) { continue; } diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java index 19b798da5aabe..b7bbcd7562cd9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java @@ -134,15 +134,36 @@ public class JobSchedulerEconomicPolicyTest { mEconomicPolicy.getInitialSatiatedConsumptionLimit()); assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getHardSatiatedConsumptionLimit()); + final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); + assertEquals(0, mEconomicPolicy.getMinSatiatedBalance(0, pkgRestricted)); assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); - assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, - mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app")); + final String pkgExempted = "com.pkg.exempted"; when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted)); + + final String pkgUpdater = "com.pkg.updater"; + when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5); + assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES + + EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater)); + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance(0, pkgUpdater)); + // Make sure it doesn't suggest a min balance greater than max. + final int updateCount = (int) (EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES + / EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES); + when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))) + .thenReturn(updateCount); + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater)); + + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app")); assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES, mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); } @@ -152,8 +173,10 @@ public class JobSchedulerEconomicPolicyTest { setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25)); setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10)); - setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9)); - setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER, + arcToCake(1)); assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit()); @@ -163,8 +186,12 @@ public class JobSchedulerEconomicPolicyTest { assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app")); final String pkgExempted = "com.pkg.exempted"; when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); - assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); - assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + final String pkgUpdater = "com.pkg.updater"; + when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3); + assertEquals(arcToCake(4) + 3 * arcToCake(1), + mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater)); } @Test @@ -175,6 +202,8 @@ public class JobSchedulerEconomicPolicyTest { setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER, + arcToCake(-4)); assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit()); @@ -186,6 +215,10 @@ public class JobSchedulerEconomicPolicyTest { when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + final String pkgUpdater = "com.pkg.updater"; + when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5); + assertEquals(arcToCake(0) + 5 * arcToCake(0), + mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater)); // Test min+max reversed. setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));