Fix bug in Ledger.

Avoid an IndexOutOfBoundsException by checking that the size of the
transactions list is greater than 0 before trying to access the 0th
element.

Bug: 158300259
Test: atest FrameworksServicesTests:LedgerTest
Change-Id: I76df357f0cf65f5577ed1c625579a6cb656264d9
This commit is contained in:
Kweku Adams
2021-08-05 11:44:44 -07:00
parent f8dd173528
commit 7637292d4b
5 changed files with 81 additions and 14 deletions

View File

@@ -26,6 +26,7 @@ import static com.android.server.tare.EconomicPolicy.TYPE_ACTION;
import static com.android.server.tare.EconomicPolicy.TYPE_REWARD;
import static com.android.server.tare.EconomicPolicy.eventToString;
import static com.android.server.tare.EconomicPolicy.getEventType;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static com.android.server.tare.TareUtils.narcToString;
import android.annotation.NonNull;
@@ -183,7 +184,7 @@ class Agent {
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
final long nowElapsed = SystemClock.elapsedRealtime();
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
mTotalDeltaCalculator.reset(ledger, nowElapsed, now);
ongoingEvents.forEach(mTotalDeltaCalculator);
balance += mTotalDeltaCalculator.mTotal;
@@ -194,7 +195,7 @@ class Agent {
@GuardedBy("mLock")
void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName,
final int eventId, @Nullable String tag) {
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
final Ledger ledger = getLedgerLocked(userId, pkgName);
final int eventType = getEventType(eventId);
@@ -278,7 +279,7 @@ class Agent {
@GuardedBy("mLock")
void onDeviceStateChangedLocked() {
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> {
@@ -326,7 +327,7 @@ class Agent {
@GuardedBy("mLock")
void onAppStatesChangedLocked(final int userId, @NonNull ArraySet<String> pkgNames) {
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
for (int i = 0; i < pkgNames.size(); ++i) {
@@ -477,7 +478,7 @@ class Agent {
// The earliest transaction won't change until we clean up the ledger, so no point
// continuing to reschedule an existing cleanup.
final long cleanupAlarmElapsed = SystemClock.elapsedRealtime() + MAX_TRANSACTION_AGE_MS
- (System.currentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
- (getCurrentTimeMillis() - ledger.getEarliestTransaction().endTimeMs);
mLedgerCleanupAlarmListener.addAlarmLocked(userId, pkgName, cleanupAlarmElapsed);
}
// TODO: save changes to disk in a background thread
@@ -509,7 +510,7 @@ class Agent {
@GuardedBy("mLock")
void reclaimUnusedAssetsLocked(double percentage) {
final List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final int userId = UserHandle.getUserId(pkgs.get(i).applicationInfo.uid);
final String pkgName = pkgs.get(i).packageName;
@@ -543,7 +544,7 @@ class Agent {
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final PackageInfo pkgInfo = pkgs.get(i);
final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
@@ -578,7 +579,7 @@ class Agent {
List<PackageInfo> pkgs = packageManager.getInstalledPackagesAsUser(0, userId);
final long maxBirthright =
mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
final PackageInfo packageInfo = pkgs.get(i);
@@ -609,7 +610,7 @@ class Agent {
List<PackageInfo> pkgs = mIrs.getInstalledPackages();
final int numPackages = pkgs.size();
final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
recordTransactionLocked(userId, pkgName, ledger,
new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null,

View File

@@ -19,6 +19,8 @@ package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
@@ -159,7 +161,7 @@ public class InternalResourceService extends SystemService {
public void onAlarm() {
synchronized (mLock) {
mAgent.reclaimUnusedAssetsLocked(DEFAULT_UNUSED_RECLAMATION_PERCENTAGE);
mLastUnusedReclamationTime = System.currentTimeMillis();
mLastUnusedReclamationTime = getCurrentTimeMillis();
scheduleUnusedWealthReclamationLocked();
}
}
@@ -342,7 +344,7 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private void scheduleUnusedWealthReclamationLocked() {
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
final long nextReclamationTime =
Math.max(mLastUnusedReclamationTime + UNUSED_RECLAMATION_PERIOD_MS, now + 30_000);
mHandler.post(() -> {
@@ -581,7 +583,7 @@ public class InternalResourceService extends SystemService {
return;
}
final long nowElapsed = SystemClock.elapsedRealtime();
final long now = System.currentTimeMillis();
final long now = getCurrentTimeMillis();
synchronized (mLock) {
mAgent.stopOngoingActionLocked(userId, pkgName, eventId, tag, nowElapsed, now);
}

View File

@@ -19,6 +19,7 @@ package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.tare.TareUtils.dumpTime;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static com.android.server.tare.TareUtils.narcToString;
import android.annotation.NonNull;
@@ -109,8 +110,8 @@ class Ledger {
/** Deletes transactions that are older than {@code minAgeMs}. */
void removeOldTransactions(long minAgeMs) {
final long cutoff = System.currentTimeMillis() - minAgeMs;
while (mTransactions.get(0).endTimeMs <= cutoff) {
final long cutoff = getCurrentTimeMillis() - minAgeMs;
while (mTransactions.size() > 0 && mTransactions.get(0).endTimeMs <= cutoff) {
mTransactions.remove(0);
}
}

View File

@@ -20,7 +20,10 @@ import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
import java.text.SimpleDateFormat;
import java.time.Clock;
class TareUtils {
private static final long NARC_IN_ARC = 1_000_000_000L;
@@ -29,6 +32,9 @@ class TareUtils {
private static final SimpleDateFormat sDumpDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@VisibleForTesting
static Clock sSystemClock = Clock.systemUTC();
static long arcToNarc(int arcs) {
return arcs * NARC_IN_ARC;
}
@@ -37,6 +43,10 @@ class TareUtils {
pw.print(sDumpDateFormat.format(time));
}
static long getCurrentTimeMillis() {
return sSystemClock.millis();
}
static int narcToArc(long narcs) {
return (int) (narcs / NARC_IN_ARC);
}

View File

@@ -19,19 +19,31 @@ package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.time.Clock;
import java.time.ZoneOffset;
/** Test that the ledger records transactions correctly. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LedgerTest {
@Before
public void setUp() {
TareUtils.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
}
@Test
public void testInitialState() {
final Ledger ledger = new Ledger();
@@ -72,4 +84,45 @@ public class LedgerTest {
assertEquals(1, ledger.get24HourSum(1, 27 * HOUR_IN_MILLIS));
assertEquals(0, ledger.get24HourSum(1, 28 * HOUR_IN_MILLIS));
}
@Test
public void testRemoveOldTransactions() {
final Ledger ledger = new Ledger();
ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
assertNull(ledger.getEarliestTransaction());
final long now = getCurrentTimeMillis();
Ledger.Transaction transaction1 = new Ledger.Transaction(
now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800);
Ledger.Transaction transaction2 = new Ledger.Transaction(
now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600);
Ledger.Transaction transaction3 = new Ledger.Transaction(
now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600);
// Instant event
Ledger.Transaction transaction4 = new Ledger.Transaction(
now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500);
// Recent event
Ledger.Transaction transaction5 = new Ledger.Transaction(
now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400);
ledger.recordTransaction(transaction1);
ledger.recordTransaction(transaction2);
ledger.recordTransaction(transaction3);
ledger.recordTransaction(transaction4);
ledger.recordTransaction(transaction5);
assertEquals(transaction1, ledger.getEarliestTransaction());
ledger.removeOldTransactions(24 * HOUR_IN_MILLIS);
assertEquals(transaction2, ledger.getEarliestTransaction());
ledger.removeOldTransactions(23 * HOUR_IN_MILLIS);
assertEquals(transaction3, ledger.getEarliestTransaction());
// Shouldn't delete transaction3 yet since there's still a piece of it within the min age
// window.
ledger.removeOldTransactions(21 * HOUR_IN_MILLIS + 30 * MINUTE_IN_MILLIS);
assertEquals(transaction3, ledger.getEarliestTransaction());
// Instant event should be removed as soon as we hit the exact threshold.
ledger.removeOldTransactions(20 * HOUR_IN_MILLIS);
assertEquals(transaction5, ledger.getEarliestTransaction());
ledger.removeOldTransactions(0);
assertNull(ledger.getEarliestTransaction());
}
}