From e56b39071698a7146e6d93b384970111a2e21a60 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 25 Jul 2022 17:50:47 +0000 Subject: [PATCH] Update EconomicPolicy loading. 1. Fix bug in CompleteEconomicPolicy that returned the incorrect hard consumption limit. 2. Add lower limits on the policy values to ensure valid values that allow for reasonable operation. Bug: 240162678 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: I89bae508930564ee3a082027dfb97ec517101dd0 --- .../tare/AlarmManagerEconomicPolicy.java | 30 +-- .../server/tare/CompleteEconomicPolicy.java | 58 ++++-- .../android/server/tare/EconomicPolicy.java | 26 ++- .../tare/JobSchedulerEconomicPolicy.java | 32 +-- .../tare/AlarmManagerEconomicPolicyTest.java | 194 ++++++++++++++++++ .../tare/CompleteEconomicPolicyTest.java | 179 ++++++++++++++++ .../tare/JobSchedulerEconomicPolicyTest.java | 194 ++++++++++++++++++ 7 files changed, 662 insertions(+), 51 deletions(-) create mode 100644 services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java create mode 100644 services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java create mode 100644 services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index d0f719b13b895..a46430feb688b 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -91,6 +91,7 @@ import static android.app.tare.EconomyManager.KEY_AM_REWARD_TOP_ACTIVITY_ONGOING import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_MAX; import static android.app.tare.EconomyManager.KEY_AM_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.arcToCake; import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS; import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; @@ -103,7 +104,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; import android.util.Slog; @@ -150,13 +150,15 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { private final KeyValueListParser mParser = new KeyValueListParser(','); private final InternalResourceService mInternalResourceService; + private final Injector mInjector; private final SparseArray mActions = new SparseArray<>(); private final SparseArray mRewards = new SparseArray<>(); - AlarmManagerEconomicPolicy(InternalResourceService irs) { + AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) { super(irs); mInternalResourceService = irs; + mInjector = injector; loadConstants("", null); } @@ -164,7 +166,7 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { void setup(@NonNull DeviceConfig.Properties properties) { super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_ALARM_MANAGER_CONSTANTS), + loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS), properties); } @@ -226,20 +228,20 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, - KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, - KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, + mMinSatiatedBalanceOther); mMaxSatiatedBalance = getConstantAsCake(mParser, properties, - KEY_AM_MAX_SATIATED_BALANCE, - DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES); + KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES, + Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES); - mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - getConstantAsCake(mParser, properties, - KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES)); + KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, + arcToCake(1)); + mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, + mInitialSatiatedConsumptionLimit); final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index c3eb5bf51161c..5d9cce84a7acf 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -23,11 +23,13 @@ import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.SparseArray; -import libcore.util.EmptyArray; +import com.android.internal.annotations.VisibleForTesting; +import libcore.util.EmptyArray; /** Combines all enabled policies into one. */ public class CompleteEconomicPolicy extends EconomicPolicy { + private final ArraySet mEnabledEconomicPolicies = new ArraySet<>(); /** Lazily populated set of actions covered by this policy. */ private final SparseArray mActions = new SparseArray<>(); @@ -35,12 +37,23 @@ public class CompleteEconomicPolicy extends EconomicPolicy { private final SparseArray mRewards = new SparseArray<>(); private final int[] mCostModifiers; private long mMaxSatiatedBalance; - private long mConsumptionLimit; + private long mInitialConsumptionLimit; + private long mHardConsumptionLimit; CompleteEconomicPolicy(@NonNull InternalResourceService irs) { + this(irs, new CompleteInjector()); + } + + @VisibleForTesting + CompleteEconomicPolicy(@NonNull InternalResourceService irs, + @NonNull CompleteInjector injector) { super(irs); - mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(irs)); - mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(irs)); + if (injector.isPolicyEnabled(POLICY_AM)) { + mEnabledEconomicPolicies.add(new AlarmManagerEconomicPolicy(irs, injector)); + } + if (injector.isPolicyEnabled(POLICY_JS)) { + mEnabledEconomicPolicies.add(new JobSchedulerEconomicPolicy(irs, injector)); + } ArraySet costModifiers = new ArraySet<>(); for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { @@ -54,7 +67,7 @@ public class CompleteEconomicPolicy extends EconomicPolicy { mCostModifiers[i] = costModifiers.valueAt(i); } - updateMaxBalances(); + updateLimits(); } @Override @@ -63,21 +76,22 @@ public class CompleteEconomicPolicy extends EconomicPolicy { for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { mEnabledEconomicPolicies.valueAt(i).setup(properties); } - updateMaxBalances(); + updateLimits(); } - private void updateMaxBalances() { - long max = 0; + private void updateLimits() { + long maxSatiatedBalance = 0; + long initialConsumptionLimit = 0; + long hardConsumptionLimit = 0; for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance(); + final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i); + maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance(); + initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit(); + hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit(); } - mMaxSatiatedBalance = max; - - max = 0; - for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit(); - } - mConsumptionLimit = max; + mMaxSatiatedBalance = maxSatiatedBalance; + mInitialConsumptionLimit = initialConsumptionLimit; + mHardConsumptionLimit = hardConsumptionLimit; } @Override @@ -96,12 +110,12 @@ public class CompleteEconomicPolicy extends EconomicPolicy { @Override long getInitialSatiatedConsumptionLimit() { - return mConsumptionLimit; + return mInitialConsumptionLimit; } @Override long getHardSatiatedConsumptionLimit() { - return mConsumptionLimit; + return mHardConsumptionLimit; } @NonNull @@ -156,6 +170,14 @@ public class CompleteEconomicPolicy extends EconomicPolicy { return reward; } + @VisibleForTesting + static class CompleteInjector extends Injector { + + boolean isPolicyEnabled(int policy) { + return true; + } + } + @Override void dump(IndentingPrintWriter pw) { dumpActiveModifiers(pw); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index d401373c00663..c3855f0c71f08 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -29,10 +29,14 @@ import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentResolver; import android.provider.DeviceConfig; +import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; +import com.android.internal.annotations.VisibleForTesting; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -415,18 +419,34 @@ public abstract class EconomicPolicy { protected long getConstantAsCake(@NonNull KeyValueListParser parser, @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) { + return getConstantAsCake(parser, properties, key, defaultValCake, 0); + } + + protected long getConstantAsCake(@NonNull KeyValueListParser parser, + @Nullable DeviceConfig.Properties properties, String key, long defaultValCake, + long minValCake) { // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig // config can cause issues since the scales may be different, so use one or the other. if (parser.size() > 0) { // User settings take precedence. Just stick with the Settings constants, even if there // are invalid values. It's not worth the time to evaluate all the key/value pairs to // make sure there are valid ones before deciding. - return parseCreditValue(parser.getString(key, null), defaultValCake); + return Math.max(minValCake, + parseCreditValue(parser.getString(key, null), defaultValCake)); } if (properties != null) { - return parseCreditValue(properties.getString(key, null), defaultValCake); + return Math.max(minValCake, + parseCreditValue(properties.getString(key, null), defaultValCake)); + } + return Math.max(minValCake, defaultValCake); + } + + @VisibleForTesting + static class Injector { + @Nullable + String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) { + return Settings.Global.getString(resolver, name); } - return defaultValCake; } protected static void dumpActiveModifiers(IndentingPrintWriter 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 948f0a71510c8..e7db1adc859e5 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -100,6 +100,7 @@ import static android.app.tare.EconomyManager.KEY_JS_REWARD_TOP_ACTIVITY_ONGOING import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_MAX; import static android.app.tare.EconomyManager.KEY_JS_REWARD_WIDGET_INTERACTION_ONGOING; +import static android.app.tare.EconomyManager.arcToCake; import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS; import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; @@ -112,7 +113,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; import android.provider.DeviceConfig; -import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.KeyValueListParser; import android.util.Slog; @@ -152,13 +152,15 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { private final KeyValueListParser mParser = new KeyValueListParser(','); private final InternalResourceService mInternalResourceService; + private final Injector mInjector; private final SparseArray mActions = new SparseArray<>(); private final SparseArray mRewards = new SparseArray<>(); - JobSchedulerEconomicPolicy(InternalResourceService irs) { + JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) { super(irs); mInternalResourceService = irs; + mInjector = injector; loadConstants("", null); } @@ -166,7 +168,7 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { void setup(@NonNull DeviceConfig.Properties properties) { super.setup(properties); ContentResolver resolver = mInternalResourceService.getContext().getContentResolver(); - loadConstants(Settings.Global.getString(resolver, TARE_JOB_SCHEDULER_CONSTANTS), + loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS), properties); } @@ -223,22 +225,20 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, - KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, - DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES); mMinSatiatedBalanceOther = getConstantAsCake(mParser, properties, - KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, - DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES); + mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties, + KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, + mMinSatiatedBalanceOther); mMaxSatiatedBalance = getConstantAsCake(mParser, properties, - KEY_JS_MAX_SATIATED_BALANCE, - DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES); + KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_JS_INITIAL_CONSUMPTION_LIMIT, - DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES); - mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, - getConstantAsCake(mParser, properties, - KEY_JS_HARD_CONSUMPTION_LIMIT, - DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES)); + KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, + arcToCake(1)); + mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, + mInitialSatiatedConsumptionLimit); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, getConstantAsCake(mParser, properties, diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java new file mode 100644 index 0000000000000..2e200c395e9d9 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tare; + +import static android.app.tare.EconomyManager.arcToCake; +import static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.app.tare.EconomyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.os.BatteryManager; +import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; +import android.provider.DeviceConfig; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +@RunWith(AndroidJUnit4.class) +public class AlarmManagerEconomicPolicyTest { + private AlarmManagerEconomicPolicy mEconomicPolicy; + private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; + private EconomicPolicy.Injector mInjector = new InjectorForTest(); + + private MockitoSession mMockingSession; + @Mock + private Context mContext; + @Mock + private InternalResourceService mIrs; + + private static class InjectorForTest extends EconomicPolicy.Injector { + public String settingsConstant; + + @Nullable + @Override + String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) { + return TARE_ALARM_MANAGER_CONSTANTS.equals(name) ? settingsConstant : null; + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(DeviceConfig.class) + .startMocking(); + + when(mIrs.getContext()).thenReturn(mContext); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); + // Called by Modifiers. + when(mContext.getSystemService(BatteryManager.class)) + .thenReturn(mock(BatteryManager.class)); + when(mContext.getSystemService(PowerManager.class)) + .thenReturn(mock(PowerManager.class)); + IActivityManager activityManager = ActivityManager.getService(); + spyOn(activityManager); + try { + doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any()); + } catch (RemoteException e) { + fail("registerUidObserver threw exception: " + e.getMessage()); + } + + mDeviceConfigPropertiesBuilder = + new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE); + doAnswer( + (Answer) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.any())); + + // Initialize real objects. + // Capture the listeners. + mEconomicPolicy = new AlarmManagerEconomicPolicy(mIrs, mInjector); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void setDeviceConfigCakes(String key, long valCakes) { + mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c"); + mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build()); + } + + @Test + public void testDefaults() { + assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance()); + final String pkgExempted = "com.pkg.exempted"; + when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); + assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } + + @Test + public void testConstantsUpdating_ValidValues() { + setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(25)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7)); + + assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance()); + 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")); + } + + @Test + public void testConstantsUpdating_InvalidValues() { + // Test negatives. + setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3)); + + assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance()); + final String pkgExempted = "com.pkg.exempted"; + 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")); + + // Test min+max reversed. + setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13)); + + assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance()); + assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java new file mode 100644 index 0000000000000..45c97e4c5d80f --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tare; + +import static android.app.tare.EconomyManager.arcToCake; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.app.tare.EconomyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.os.BatteryManager; +import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; +import android.provider.DeviceConfig; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +@RunWith(AndroidJUnit4.class) +public class CompleteEconomicPolicyTest { + private CompleteEconomicPolicy mEconomicPolicy; + private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; + private final CompleteEconomicPolicy.CompleteInjector mInjector = new InjectorForTest(); + + private MockitoSession mMockingSession; + @Mock + private Context mContext; + @Mock + private InternalResourceService mIrs; + + private static class InjectorForTest extends CompleteEconomicPolicy.CompleteInjector { + public String settingsConstant; + + @Nullable + @Override + String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) { + return settingsConstant; + } + + @Override + boolean isPolicyEnabled(int policy) { + // Use a limited set of policies so that the test doesn't need to be updated whenever + // a policy is added or removed. + return policy == EconomicPolicy.POLICY_AM || policy == EconomicPolicy.POLICY_JS; + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(DeviceConfig.class) + .startMocking(); + + when(mIrs.getContext()).thenReturn(mContext); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); + // Called by Modifiers. + when(mContext.getSystemService(BatteryManager.class)) + .thenReturn(mock(BatteryManager.class)); + when(mContext.getSystemService(PowerManager.class)) + .thenReturn(mock(PowerManager.class)); + IActivityManager activityManager = ActivityManager.getService(); + spyOn(activityManager); + try { + doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any()); + } catch (RemoteException e) { + fail("registerUidObserver threw exception: " + e.getMessage()); + } + + mDeviceConfigPropertiesBuilder = + new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE); + doAnswer( + (Answer) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.any())); + + // Initialize real objects. + // Capture the listeners. + mEconomicPolicy = new CompleteEconomicPolicy(mIrs, mInjector); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void setDeviceConfigCakes(String key, long valCakes) { + mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c"); + mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build()); + } + + @Test + public void testDefaults() { + assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES + + EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES + + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES + + EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance()); + final String pkgExempted = "com.pkg.exempted"; + when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); + assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES + + EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES + + EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } + + @Test + public void testConstantsUpdated() { + setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4)); + setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(6)); + setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(24)); + setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(26)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(9)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2)); + + assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance()); + final String pkgExempted = "com.pkg.exempted"; + when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true); + assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java new file mode 100644 index 0000000000000..03ce91aea58b5 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.tare; + +import static android.app.tare.EconomyManager.arcToCake; +import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.app.tare.EconomyManager; +import android.content.ContentResolver; +import android.content.Context; +import android.os.BatteryManager; +import android.os.Looper; +import android.os.PowerManager; +import android.os.RemoteException; +import android.provider.DeviceConfig; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +@RunWith(AndroidJUnit4.class) +public class JobSchedulerEconomicPolicyTest { + private JobSchedulerEconomicPolicy mEconomicPolicy; + private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; + private final EconomicPolicy.Injector mInjector = new InjectorForTest(); + + private MockitoSession mMockingSession; + @Mock + private Context mContext; + @Mock + private InternalResourceService mIrs; + + private static class InjectorForTest extends EconomicPolicy.Injector { + public String settingsConstant; + + @Nullable + @Override + String getSettingsGlobalString(@NonNull ContentResolver resolver, @NonNull String name) { + return TARE_JOB_SCHEDULER_CONSTANTS.equals(name) ? settingsConstant : null; + } + } + + @Before + public void setUp() { + mMockingSession = mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(DeviceConfig.class) + .startMocking(); + + when(mIrs.getContext()).thenReturn(mContext); + when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); + when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class)); + // Called by Modifiers. + when(mContext.getSystemService(BatteryManager.class)) + .thenReturn(mock(BatteryManager.class)); + when(mContext.getSystemService(PowerManager.class)) + .thenReturn(mock(PowerManager.class)); + IActivityManager activityManager = ActivityManager.getService(); + spyOn(activityManager); + try { + doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any()); + } catch (RemoteException e) { + fail("registerUidObserver threw exception: " + e.getMessage()); + } + + mDeviceConfigPropertiesBuilder = + new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TARE); + doAnswer( + (Answer) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.any())); + + // Initialize real objects. + // Capture the listeners. + mEconomicPolicy = new JobSchedulerEconomicPolicy(mIrs, mInjector); + } + + @After + public void tearDown() { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + private void setDeviceConfigCakes(String key, long valCakes) { + mDeviceConfigPropertiesBuilder.setString(key, valCakes + "c"); + mEconomicPolicy.setup(mDeviceConfigPropertiesBuilder.build()); + } + + @Test + public void testDefaults() { + assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, + mEconomicPolicy.getMaxSatiatedBalance()); + 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_MIN_SATIATED_BALANCE_OTHER_APP_CAKES, + mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } + + @Test + public void testConstantsUpdating_ValidValues() { + 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)); + + assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance()); + 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")); + } + + @Test + public void testConstantsUpdating_InvalidValues() { + // Test negatives. + setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(-5)); + 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)); + + assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance()); + final String pkgExempted = "com.pkg.exempted"; + 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")); + + // Test min+max reversed. + setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13)); + + assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance()); + assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); + assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); + } +}