Merge "Update EconomicPolicy loading."

This commit is contained in:
TreeHugger Robot
2022-07-29 16:30:33 +00:00
committed by Android (Google) Code Review
7 changed files with 662 additions and 51 deletions

View File

@@ -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,

View File

@@ -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();
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);

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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"));
}
}

View File

@@ -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"));
}
}

View File

@@ -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"));
}
}