diff options
7 files changed, 662 insertions, 51 deletions
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 d0f719b13b89..a46430feb688 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<Action> mActions = new SparseArray<>(); private final SparseArray<Reward> 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 c3eb5bf51161..5d9cce84a7ac 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<EconomicPolicy> mEnabledEconomicPolicies = new ArraySet<>(); /** Lazily populated set of actions covered by this policy. */ private final SparseArray<Action> mActions = new SparseArray<>(); @@ -35,12 +37,23 @@ public class CompleteEconomicPolicy extends EconomicPolicy { private final SparseArray<Reward> 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<Integer> 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(); - } - mMaxSatiatedBalance = max; - - max = 0; - for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit(); + final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i); + maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance(); + initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit(); + hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit(); } - 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 aeb6abc3ce1b..0937e7ba4055 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; @@ -419,18 +423,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 948f0a71510c..e7db1adc859e 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<Action> mActions = new SparseArray<>(); private final SparseArray<Reward> 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 000000000000..2e200c395e9d --- /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<DeviceConfig.Properties>) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>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 000000000000..45c97e4c5d80 --- /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<DeviceConfig.Properties>) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>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 000000000000..03ce91aea58b --- /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<DeviceConfig.Properties>) invocationOnMock + -> mDeviceConfigPropertiesBuilder.build()) + .when(() -> DeviceConfig.getProperties( + eq(DeviceConfig.NAMESPACE_TARE), ArgumentMatchers.<String>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")); + } +} |