Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7637292d authored by Kweku Adams's avatar Kweku Adams
Browse files

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
parent f8dd1735
Loading
Loading
Loading
Loading
+10 −9
Original line number Diff line number Diff line
@@ -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,
+5 −3
Original line number Diff line number Diff line
@@ -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);
            }
+3 −2
Original line number Diff line number Diff line
@@ -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);
        }
    }
+10 −0
Original line number Diff line number Diff line
@@ -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);
    }
+53 −0
Original line number Diff line number Diff line
@@ -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());
    }
}