Merge "Add shell command so tests aren't hindered."

This commit is contained in:
TreeHugger Robot
2022-08-16 04:34:00 +00:00
committed by Android (Google) Code Review
7 changed files with 292 additions and 24 deletions

View File

@@ -44,6 +44,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -282,6 +283,7 @@ class Agent {
for (int i = 0; i < pkgNames.size(); ++i) {
final String pkgName = pkgNames.valueAt(i);
final boolean isVip = mIrs.isVip(userId, pkgName);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
@@ -296,8 +298,8 @@ class Agent {
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -310,6 +312,51 @@ class Agent {
}
}
@GuardedBy("mLock")
void onVipStatusChangedLocked(final int userId, @NonNull String pkgName) {
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final boolean isVip = mIrs.isVip(userId, pkgName);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
ongoingEvents.forEach(mOngoingEventUpdater);
}
final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
mActionAffordabilityNotes.get(userId, pkgName);
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance =
mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
mIrs.postAffordabilityChanged(userId, pkgName, note);
}
}
}
scheduleBalanceCheckLocked(userId, pkgName);
}
@GuardedBy("mLock")
void onVipStatusChangedLocked(@NonNull SparseSetArray<String> pkgs) {
for (int u = pkgs.size() - 1; u >= 0; --u) {
final int userId = pkgs.keyAt(u);
for (int p = pkgs.sizeAt(u) - 1; p >= 0; --p) {
onVipStatusChangedLocked(userId, pkgs.valueAt(u, p));
}
}
}
@GuardedBy("mLock")
private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
final long now = getCurrentTimeMillis();
@@ -347,11 +394,12 @@ class Agent {
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance = getBalanceLocked(userId, pkgName);
final boolean isVip = mIrs.isVip(userId, pkgName);
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
final boolean isAffordable =
isAffordableLocked(newBalance,
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -452,6 +500,14 @@ class Agent {
"Tried to adjust system balance for " + appToString(userId, pkgName));
return;
}
if (mIrs.isVip(userId, pkgName)) {
// This could happen if the app was made a VIP after it started performing actions.
// Continue recording the transaction for debugging purposes, but don't let it change
// any numbers.
transaction = new Ledger.Transaction(
transaction.startTimeMs, transaction.endTimeMs,
transaction.eventId, transaction.tag, 0 /* delta */, transaction.ctp);
}
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long originalBalance = ledger.getCurrentBalance();
if (transaction.delta > 0
@@ -477,10 +533,11 @@ class Agent {
mActionAffordabilityNotes.get(userId, pkgName);
if (actionAffordabilityNotes != null) {
final long newBalance = ledger.getCurrentBalance();
final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
final boolean isAffordable =
isAffordableLocked(newBalance,
final boolean isAffordable = isVip
|| isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -866,7 +923,7 @@ class Agent {
private void scheduleBalanceCheckLocked(final int userId, @NonNull final String pkgName) {
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents == null) {
if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
// No ongoing transactions. No reason to schedule
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
return;
@@ -1059,9 +1116,10 @@ class Agent {
note.setNewAffordability(true);
return;
}
final boolean isVip = mIrs.isVip(userId, pkgName);
note.recalculateCosts(economicPolicy, userId, pkgName);
note.setNewAffordability(
isAffordableLocked(getBalanceLocked(userId, pkgName),
note.setNewAffordability(isVip
|| isAffordableLocked(getBalanceLocked(userId, pkgName),
note.getCachedModifiedPrice(), note.getCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
@@ -1200,11 +1258,12 @@ class Agent {
if (actionAffordabilityNotes != null
&& actionAffordabilityNotes.size() > 0) {
final long newBalance = getBalanceLocked(userId, pkgName);
final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
final boolean isAffordable = isAffordableLocked(
final boolean isAffordable = isVip || isAffordableLocked(
newBalance, note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);

View File

@@ -149,7 +149,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -157,7 +156,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -165,14 +163,14 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account

View File

@@ -169,9 +169,11 @@ public abstract class EconomicPolicy {
}
}
protected final InternalResourceService mIrs;
private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
EconomicPolicy(@NonNull InternalResourceService irs) {
mIrs = irs;
for (int mId : getCostModifiers()) {
initModifier(mId, irs);
}
@@ -240,7 +242,7 @@ public abstract class EconomicPolicy {
@NonNull
final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
final Action action = getAction(actionId);
if (action == null) {
if (action == null || mIrs.isVip(userId, pkgName)) {
return new Cost(0, 0);
}
long ctp = action.costToProduce;

View File

@@ -48,6 +48,7 @@ import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -149,6 +150,9 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private ArraySet<String> mExemptedApps = new ArraySet<>();
@GuardedBy("mLock")
private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
private volatile boolean mExemptListLoaded;
@@ -368,6 +372,21 @@ public class InternalResourceService extends SystemService {
return UserHandle.isCore(getUid(userId, pkgName));
}
boolean isVip(final int userId, @NonNull String pkgName) {
synchronized (mLock) {
final Boolean override = mVipOverrides.get(userId, pkgName);
if (override != null) {
return override;
}
}
if (isSystem(userId, pkgName)) {
// The government, I mean the system, can create ARCs as it needs to in order to
// operate.
return true;
}
return false;
}
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
@@ -470,6 +489,7 @@ public class InternalResourceService extends SystemService {
}
synchronized (mLock) {
mUidToPackageCache.remove(uid, pkgName);
mVipOverrides.delete(userId, pkgName);
for (int i = 0; i < mPkgCache.size(); ++i) {
final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
if (UserHandle.getUserId(pkgInfo.uid) == userId
@@ -506,6 +526,7 @@ public class InternalResourceService extends SystemService {
void onUserRemoved(final int userId) {
synchronized (mLock) {
mVipOverrides.delete(userId);
ArrayList<String> removedPkgs = new ArrayList<>();
for (int i = mPkgCache.size() - 1; i >= 0; --i) {
final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
@@ -886,6 +907,15 @@ public class InternalResourceService extends SystemService {
Binder.restoreCallingIdentity(identityToken);
}
}
@Override
public int handleShellCommand(@NonNull ParcelFileDescriptor in,
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
return (new TareShellCommand(InternalResourceService.this)).exec(
this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
args);
}
}
private final class LocalService implements EconomyManagerInternal {
@@ -937,9 +967,9 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return true;
}
if (isSystem(userId, pkgName)) {
if (isVip(userId, pkgName)) {
// The government, I mean the system, can create ARCs as it needs to in order to
// operate.
// allow VIPs to operate.
return true;
}
// TODO: take temp-allowlist into consideration
@@ -965,7 +995,7 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return FOREVER_MS;
}
if (isSystem(userId, pkgName)) {
if (isVip(userId, pkgName)) {
return FOREVER_MS;
}
long totalCostPerSecond = 0;
@@ -1136,6 +1166,47 @@ public class InternalResourceService extends SystemService {
}
}
// Shell command infrastructure
int executeClearVip(@NonNull PrintWriter pw) {
synchronized (mLock) {
final SparseSetArray<String> changedPkgs = new SparseSetArray<>();
for (int u = mVipOverrides.numMaps() - 1; u >= 0; --u) {
final int userId = mVipOverrides.keyAt(u);
for (int p = mVipOverrides.numElementsForKeyAt(u) - 1; p >= 0; --p) {
changedPkgs.add(userId, mVipOverrides.keyAt(u, p));
}
}
mVipOverrides.clear();
if (mIsEnabled) {
mAgent.onVipStatusChangedLocked(changedPkgs);
}
}
pw.println("Cleared all VIP statuses");
return TareShellCommand.COMMAND_SUCCESS;
}
int executeSetVip(@NonNull PrintWriter pw,
int userId, @NonNull String pkgName, @Nullable Boolean newVipState) {
final boolean changed;
synchronized (mLock) {
final boolean wasVip = isVip(userId, pkgName);
if (newVipState == null) {
mVipOverrides.delete(userId, pkgName);
} else {
mVipOverrides.add(userId, pkgName, newVipState);
}
changed = isVip(userId, pkgName) != wasVip;
if (mIsEnabled && changed) {
mAgent.onVipStatusChangedLocked(userId, pkgName);
}
}
pw.println(appToString(userId, pkgName) + " VIP status set to " + newVipState + "."
+ " Final VIP state changed? " + changed);
return TareShellCommand.COMMAND_SUCCESS;
}
// Dump infrastructure
private static void dumpHelp(PrintWriter pw) {
pw.println("Resource Economy (economy) dump options:");
pw.println(" [-h|--help] [package] ...");
@@ -1173,6 +1244,29 @@ public class InternalResourceService extends SystemService {
pw.print("Exempted apps", mExemptedApps);
pw.println();
boolean printedVips = false;
pw.println();
pw.print("VIPs:");
for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
final int userId = mVipOverrides.keyAt(u);
for (int p = 0; p < mVipOverrides.numElementsForKeyAt(u); ++p) {
final String pkgName = mVipOverrides.keyAt(u, p);
printedVips = true;
pw.println();
pw.print(appToString(userId, pkgName));
pw.print("=");
pw.print(mVipOverrides.valueAt(u, p));
}
}
if (printedVips) {
pw.println();
} else {
pw.print(" None");
}
pw.println();
pw.println();
mCompleteEconomicPolicy.dump(pw);

View File

@@ -151,7 +151,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -159,7 +158,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -167,14 +165,14 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
final ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account

View File

@@ -0,0 +1,112 @@
/*
* 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 android.Manifest;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import android.os.Binder;
import com.android.modules.utils.BasicShellCommandHandler;
import java.io.PrintWriter;
/**
* Shell command handler for TARE.
*/
public class TareShellCommand extends BasicShellCommandHandler {
static final int COMMAND_ERROR = -1;
static final int COMMAND_SUCCESS = 0;
private final InternalResourceService mIrs;
public TareShellCommand(@NonNull InternalResourceService irs) {
mIrs = irs;
}
@Override
public int onCommand(String cmd) {
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd != null ? cmd : "") {
case "clear-vip":
return runClearVip(pw);
case "set-vip":
return runSetVip(pw);
default:
return handleDefaultCommands(cmd);
}
} catch (Exception e) {
pw.println("Exception: " + e);
}
return COMMAND_ERROR;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("TARE commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" clear-vip");
pw.println(" Clears all VIP settings resulting from previous calls using `set-vip` and");
pw.println(" resets them all to default.");
pw.println(" set-vip <USER_ID> <PACKAGE> <true|false|default>");
pw.println(" Designate the app as a Very Important Package or not. A VIP is allowed to");
pw.println(" do as much work as it wants, regardless of TARE state.");
pw.println(" The user ID must be an explicit user ID. USER_ALL, CURRENT, etc. are not");
pw.println(" supported.");
pw.println();
}
private void checkPermission(@NonNull String operation) throws Exception {
final int perm = mIrs.getContext()
.checkCallingOrSelfPermission(Manifest.permission.CHANGE_APP_IDLE_STATE);
if (perm != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Uid " + Binder.getCallingUid()
+ " not permitted to " + operation);
}
}
private int runClearVip(@NonNull PrintWriter pw) throws Exception {
checkPermission("clear vip");
final long ident = Binder.clearCallingIdentity();
try {
return mIrs.executeClearVip(pw);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private int runSetVip(@NonNull PrintWriter pw) throws Exception {
checkPermission("modify vip");
final int userId = Integer.parseInt(getNextArgRequired());
final String pkgName = getNextArgRequired();
final String vipState = getNextArgRequired();
final Boolean isVip = "default".equals(vipState) ? null : Boolean.valueOf(vipState);
final long ident = Binder.clearCallingIdentity();
try {
return mIrs.executeSetVip(pw, userId, pkgName, isVip);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 The Android Open Source Project
* 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.
@@ -17,7 +17,10 @@
package com.android.server.tare;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.util.ArraySet;
import android.util.SparseLongArray;
@@ -99,7 +102,9 @@ public class AgentTrendCalculatorTest {
@Before
public void setUp() {
mEconomicPolicy = new MockEconomicPolicy(mock(InternalResourceService.class));
final InternalResourceService irs = mock(InternalResourceService.class);
when(irs.isVip(anyInt(), anyString())).thenReturn(false);
mEconomicPolicy = new MockEconomicPolicy(irs);
}
@Test