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

Commit c00c253b authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add base Agent code."

parents b0bddf2b 45c8f8c9
Loading
Loading
Loading
Loading
+228 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;

import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;

import java.util.List;

/**
 * Other half of the IRS. The agent handles the nitty gritty details, interacting directly with
 * ledgers, carrying out specific events such as tax collection and granting initial balances or
 * replenishing balances, and tracking ongoing events.
 */
class Agent {
    private static final String TAG = "TARE-" + Agent.class.getSimpleName();
    private static final boolean DEBUG = InternalResourceService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    private final CompleteEconomicPolicy mCompleteEconomicPolicy;
    private final InternalResourceService mIrs;

    @GuardedBy("mLock")
    private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>();

    @GuardedBy("mLock")
    private long mCurrentNarcsInCirculation;

    Agent(@NonNull InternalResourceService irs,
            @NonNull CompleteEconomicPolicy completeEconomicPolicy) {
        mIrs = irs;
        mCompleteEconomicPolicy = completeEconomicPolicy;
    }

    @GuardedBy("mLock")
    @NonNull
    private Ledger getLedgerLocked(final int userId, @NonNull final String pkgName) {
        Ledger ledger = mLedgers.get(userId, pkgName);
        if (ledger == null) {
            // TODO: load from disk
            ledger = new Ledger();
            mLedgers.add(userId, pkgName, ledger);
        }
        return ledger;
    }

    /** Get an app's current balance, factoring in any currently ongoing events. */
    @GuardedBy("mLock")
    private long getBalanceLocked(final int userId, @NonNull final String pkgName) {
        final Ledger ledger = getLedgerLocked(userId, pkgName);
        long balance = ledger.getCurrentBalance();
        // TODO: add ongoing events
        return balance;
    }

    @GuardedBy("mLock")
    private void recordTransactionLocked(final int userId, @NonNull final String pkgName,
            @NonNull Ledger ledger, @NonNull Ledger.Transaction transaction) {
        final long maxCirculationAllowed = mIrs.getMaxCirculationLocked();
        final long newArcsInCirculation = mCurrentNarcsInCirculation + transaction.delta;
        if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) {
            final long newDelta = maxCirculationAllowed - mCurrentNarcsInCirculation;
            Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction "
                    + transaction.reason + (transaction.tag == null ? "" : ":" + transaction.tag)
                    + " for <" + userId + ">" + pkgName + " by " + (transaction.delta - newDelta));
            transaction = new Ledger.Transaction(
                    transaction.startTimeMs, transaction.endTimeMs,
                    transaction.reason, transaction.tag, newDelta);
        }
        final long originalBalance = ledger.getCurrentBalance();
        if (transaction.delta > 0
                && originalBalance + transaction.delta
                > mCompleteEconomicPolicy.getMaxSatiatedBalance()) {
            final long newDelta = mCompleteEconomicPolicy.getMaxSatiatedBalance() - originalBalance;
            Slog.i(TAG, "Would result in becoming too rich. Decreasing transaction  "
                    + transaction.reason + (transaction.tag == null ? "" : ":" + transaction.tag)
                    + " for <" + userId + ">" + pkgName + " by " + (transaction.delta - newDelta));
            transaction = new Ledger.Transaction(
                    transaction.startTimeMs, transaction.endTimeMs,
                    transaction.reason, transaction.tag, newDelta);
        }
        ledger.recordTransaction(transaction);
        mCurrentNarcsInCirculation += transaction.delta;
        // TODO: save changes to disk in a background thread
        final long newBalance = ledger.getCurrentBalance();
        if (originalBalance <= 0 && newBalance > 0) {
            mIrs.postSolvencyChanged(userId, pkgName, true);
        } else if (originalBalance > 0 && newBalance <= 0) {
            mIrs.postSolvencyChanged(userId, pkgName, false);
        }
    }

    @GuardedBy("mLock")
    void distributeBasicIncomeLocked(int batteryLevel) {
        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
        final long now = System.currentTimeMillis();
        for (int i = 0; i < pkgs.size(); ++i) {
            final PackageInfo pkgInfo = pkgs.get(i);
            final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
            final String pkgName = pkgInfo.packageName;
            Ledger ledger = getLedgerLocked(userId, pkgName);
            final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
            final double perc = batteryLevel / 100d;
            // TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
            if (ledger.getCurrentBalance() < minBalance) {
                final long shortfall = minBalance - getBalanceLocked(userId, pkgName);
                recordTransactionLocked(userId, pkgName, ledger,
                        new Ledger.Transaction(now, now, "UNIVERSAL_BASIC_INCOME",
                                null, (long) (perc * shortfall)));
            }
        }
    }

    /** Give each app an initial balance. */
    @GuardedBy("mLock")
    void grantBirthrightsLocked() {
        UserManagerInternal userManagerInternal =
                LocalServices.getService(UserManagerInternal.class);
        final int[] userIds = userManagerInternal.getUserIds();
        for (int userId : userIds) {
            grantBirthrightsLocked(userId);
        }
    }

    @GuardedBy("mLock")
    void grantBirthrightsLocked(final int userId) {
        PackageManager packageManager = mIrs.getContext().getPackageManager();
        List<PackageInfo> pkgs = packageManager.getInstalledPackagesAsUser(0, userId);
        final long maxBirthright =
                mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size();
        final long now = System.currentTimeMillis();

        for (int i = 0; i < pkgs.size(); ++i) {
            final PackageInfo packageInfo = pkgs.get(i);
            final String pkgName = packageInfo.packageName;
            final Ledger ledger = getLedgerLocked(userId, pkgName);
            if (ledger.getCurrentBalance() > 0) {
                // App already got credits somehow. Move along.
                Slog.wtf(TAG, "App " + pkgName + " had credits before economy was set up");
                continue;
            }

            recordTransactionLocked(userId, pkgName, ledger,
                    new Ledger.Transaction(now, now, "BIRTHRIGHT", null,
                            Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))));
        }
    }

    @GuardedBy("mLock")
    void grantBirthrightLocked(final int userId, @NonNull final String pkgName) {
        final Ledger ledger = getLedgerLocked(userId, pkgName);
        if (ledger.getCurrentBalance() > 0) {
            Slog.wtf(TAG, "App " + pkgName + " had credits as soon as it was installed");
            // App already got credits somehow. Move along.
            return;
        }

        List<PackageInfo> pkgs = mIrs.getInstalledPackages();
        final int numPackages = pkgs.size();
        final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages;
        final long now = System.currentTimeMillis();

        recordTransactionLocked(userId, pkgName, ledger,
                new Ledger.Transaction(now, now, "BIRTHRIGHT", null,
                        Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))));
    }

    @GuardedBy("mLock")
    void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) {
        reclaimAssetsLocked(userId, pkgName);
    }

    /**
     * Reclaims any ARCs granted to the app, making them available to other apps. Also deletes the
     * app's ledger and stops any ongoing event tracking.
     */
    @GuardedBy("mLock")
    private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) {
        Ledger ledger = getLedgerLocked(userId, pkgName);
        if (ledger.getCurrentBalance() != 0) {
            mCurrentNarcsInCirculation -= ledger.getCurrentBalance();
        }
        // TODO: delete ledger entry from disk
        mLedgers.delete(userId, pkgName);
    }

    @GuardedBy("mLock")
    void onUserRemovedLocked(final int userId, @NonNull final List<String> pkgNames) {
        reclaimAssetsLocked(userId, pkgNames);
    }

    @GuardedBy("mLock")
    private void reclaimAssetsLocked(final int userId, @NonNull final List<String> pkgNames) {
        for (int i = 0; i < pkgNames.size(); ++i) {
            reclaimAssetsLocked(userId, pkgNames.get(i));
        }
    }

    @GuardedBy("mLock")
    void dumpLocked(IndentingPrintWriter pw) {
        pw.print("Current GDP: ");
        pw.println(mCurrentNarcsInCirculation);
    }
}
+44 −1
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
/**
 * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when
 * appropriate, and reclaiming ARCs at the right times. The IRS deals with the high level details
 * while the {@link Teller} deals with the nitty-gritty details.
 * while the {@link Agent} deals with the nitty-gritty details.
 *
 * Note on locking: Any function with the suffix 'Locked' needs to lock on {@link #mLock}.
 *
@@ -64,6 +64,9 @@ public class InternalResourceService extends SystemService {
    private final BatteryManagerInternal mBatteryManagerInternal;
    private final PackageManager mPackageManager;

    private final CompleteEconomicPolicy mCompleteEconomicPolicy;
    private final Agent mAgent;

    private final CopyOnWriteArraySet<BalanceChangeListener> mBalanceChangeListeners =
            new CopyOnWriteArraySet<>();

@@ -147,6 +150,8 @@ public class InternalResourceService extends SystemService {
        mHandler = new IrsHandler(TareHandlerThread.get().getLooper());
        mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
        mPackageManager = context.getPackageManager();
        mCompleteEconomicPolicy = new CompleteEconomicPolicy(this);
        mAgent = new Agent(this, mCompleteEconomicPolicy);

        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED);
@@ -169,6 +174,22 @@ public class InternalResourceService extends SystemService {

    }

    @Override
    public void onBootPhase(int phase) {
        if (PHASE_SYSTEM_SERVICES_READY == phase) {
            synchronized (mLock) {
                mCurrentBatteryLevel = getCurrentBatteryLevel();
                // TODO: base on if we have anything persisted
                final boolean isFirstSetup = true;
                if (isFirstSetup) {
                    mHandler.post(this::setupEconomy);
                } else {
                    mIsSetup = true;
                }
            }
        }
    }

    @NonNull
    Object getLock() {
        return mLock;
@@ -181,6 +202,17 @@ public class InternalResourceService extends SystemService {
        }
    }

    @GuardedBy("mLock")
    long getMaxCirculationLocked() {
        return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100;
    }

    @GuardedBy("mLock")
    long getMinBalanceLocked(final int userId, @NonNull final String pkgName) {
        return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMinSatiatedBalance(userId, pkgName)
                / 100;
    }

    @Nullable
    @GuardedBy("mLock")
    ArraySet<String> getPackagesForUidLocked(final int uid) {
@@ -200,6 +232,9 @@ public class InternalResourceService extends SystemService {
    void onBatteryLevelChanged() {
        synchronized (mLock) {
            final int newBatteryLevel = getCurrentBatteryLevel();
            if (newBatteryLevel > mCurrentBatteryLevel) {
                mAgent.distributeBasicIncomeLocked(mCurrentBatteryLevel);
            }
            mCurrentBatteryLevel = newBatteryLevel;
        }
    }
@@ -219,6 +254,8 @@ public class InternalResourceService extends SystemService {
        synchronized (mLock) {
            mPkgCache.add(packageInfo);
            mUidToPackageCache.add(uid, pkgName);
            // TODO: only do this when the user first launches the app (app leaves stopped state)
            mAgent.grantBirthrightLocked(userId, pkgName);
        }
    }

@@ -240,6 +277,7 @@ public class InternalResourceService extends SystemService {
                    break;
                }
            }
            mAgent.onPackageRemovedLocked(userId, pkgName);
        }
    }

@@ -249,6 +287,7 @@ public class InternalResourceService extends SystemService {
    void onUserAdded(final int userId) {
        synchronized (mLock) {
            loadInstalledPackageListLocked();
            mAgent.grantBirthrightsLocked(userId);
        }
    }

@@ -265,6 +304,7 @@ public class InternalResourceService extends SystemService {
                }
            }
            loadInstalledPackageListLocked();
            mAgent.onUserRemovedLocked(userId, removedPkgs);
        }
    }

@@ -286,6 +326,7 @@ public class InternalResourceService extends SystemService {
    private void setupEconomy() {
        synchronized (mLock) {
            loadInstalledPackageListLocked();
            mAgent.grantBirthrightsLocked();
            mIsSetup = true;
        }
    }
@@ -319,10 +360,12 @@ public class InternalResourceService extends SystemService {

        @Override
        public void registerBalanceChangeListener(@NonNull BalanceChangeListener listener) {
            mBalanceChangeListeners.add(listener);
        }

        @Override
        public void unregisterBalanceChangeListener(@NonNull BalanceChangeListener listener) {
            mBalanceChangeListeners.remove(listener);
        }

        @Override