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

Commit ca704f48 authored by Kweku Adams's avatar Kweku Adams Committed by Android (Google) Code Review
Browse files

Merge "Introduce action affordability checking."

parents 1d95457f ee8699e5
Loading
Loading
Loading
Loading
+362 −75

File changed.

Preview size limit exceeded, changes collapsed.

+103 −19
Original line number Original line Diff line number Diff line
@@ -19,41 +19,125 @@ package com.android.server.tare;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
/**
 * Interface for the system server to deal with the resource economy subsystem.
 * Interface for the system server to deal with the resource economy subsystem.
 *
 *
 * @hide
 * @hide
 */
 */
interface EconomyManagerInternal {
public interface EconomyManagerInternal {
    /** Listener for when an app changes its solvency status. */
    /**
    interface BalanceChangeListener {
     * Used to indicate a future action an app is expected to take.
     */
    final class AnticipatedAction {
        public final int actionId;
        public final int numInstantaneousCalls;
        public final long ongoingDurationMs;
        private final int mHashCode;

        /**
        /**
         * Called when an app runs out of funds.
         * @param actionId              The expected action
         * {@link #noteOngoingEventStopped(int, String, int, String)} must still be called to
         * @param numInstantaneousCalls How many instantaneous times the action will be performed
         * formally end the action.
         * @param ongoingDurationMs     An estimate of how long the ongoing event will go on for
         */
         */
        void onBankruptcy(int userId, @NonNull String pkgName);
        public AnticipatedAction(@EconomicPolicy.AppAction int actionId,
                int numInstantaneousCalls, long ongoingDurationMs) {
            this.actionId = actionId;
            this.numInstantaneousCalls = numInstantaneousCalls;
            this.ongoingDurationMs = ongoingDurationMs;

            int hash = 0;
            hash = 31 * hash + actionId;
            hash = 31 * hash + numInstantaneousCalls;
            hash = 31 * hash + (int) (ongoingDurationMs ^ (ongoingDurationMs >>> 32));
            mHashCode = hash;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            AnticipatedAction that = (AnticipatedAction) o;
            return actionId == that.actionId
                    && numInstantaneousCalls == that.numInstantaneousCalls
                    && ongoingDurationMs == that.ongoingDurationMs;
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }
    }


    /**
    /**
         * Called when an app goes from being insolvent to solvent.
     * A collection of {@link AnticipatedAction}s that will be performed together.
     */
     */
        void onSolvent(int userId, @NonNull String pkgName);
    final class ActionBill {
        private static final Comparator<AnticipatedAction>
                sAnticipatedActionComparator = Comparator.comparingInt(aa -> aa.actionId);

        private final List<AnticipatedAction> mAnticipatedActions;
        private final int mHashCode;

        public ActionBill(@NonNull List<AnticipatedAction> anticipatedActions) {
            List<AnticipatedAction> actions = new ArrayList<>(anticipatedActions);
            actions.sort(sAnticipatedActionComparator);
            mAnticipatedActions = Collections.unmodifiableList(actions);

            int hash = 0;
            for (int i = 0; i < mAnticipatedActions.size(); ++i) {
                hash = 31 * hash + mAnticipatedActions.get(i).hashCode();
            }
            }
            mHashCode = hash;
        }

        List<AnticipatedAction> getAnticipatedActions() {
            return mAnticipatedActions;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ActionBill that = (ActionBill) o;
            return mAnticipatedActions.equals(that.mAnticipatedActions);
        }

        @Override
        public int hashCode() {
            return mHashCode;
        }
    }

    /** Listener for when an app's ability to afford a bill changes. */
    interface AffordabilityChangeListener {
        void onAffordabilityChanged(int userId, @NonNull String pkgName, @NonNull ActionBill bill,
                boolean canAfford);
    }

    /**
     * Return {@code true} if the app is able to pay for the anticipated actions.
     */
    boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill);


    /** Register a {@link BalanceChangeListener} to track all apps' solvency status changes. */
    void registerBalanceChangeListener(@NonNull BalanceChangeListener listener);


    /**
    /**
     * Unregister a {@link BalanceChangeListener} from being notified of any app's solvency status
     * Register an {@link AffordabilityChangeListener} to track when an app's ability to afford the
     * changes.
     * indicated bill changes.
     */
     */
    void unregisterBalanceChangeListener(@NonNull BalanceChangeListener listener);
    void registerAffordabilityChangeListener(int userId, @NonNull String pkgName,
            @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill);


    /**
    /**
     * Return {@code true} if the app has a balance equal to or greater than the specified min
     * Unregister a {@link AffordabilityChangeListener} from being notified of any changes to an
     * balance.
     * app's ability to afford the specified bill.
     */
     */
    boolean hasBalanceAtLeast(int userId, @NonNull String pkgName, int minBalance);
    void unregisterAffordabilityChangeListener(int userId, @NonNull String pkgName,
            @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill);


    /**
    /**
     * Note that an instantaneous event has occurred. The event must be specified in one of the
     * Note that an instantaneous event has occurred. The event must be specified in one of the
+57 −33
Original line number Original line Diff line number Diff line
@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.net.Uri;
import android.os.BatteryManagerInternal;
import android.os.BatteryManagerInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
@@ -43,11 +44,9 @@ import android.util.SparseSetArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemService;
import com.android.server.tare.EconomyManagerInternal.BalanceChangeListener;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;


/**
/**
 * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when
 * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when
@@ -76,9 +75,6 @@ public class InternalResourceService extends SystemService {
    private final CompleteEconomicPolicy mCompleteEconomicPolicy;
    private final CompleteEconomicPolicy mCompleteEconomicPolicy;
    private final Agent mAgent;
    private final Agent mAgent;


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

    @NonNull
    @NonNull
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private List<PackageInfo> mPkgCache = new ArrayList<>();
    private List<PackageInfo> mPkgCache = new ArrayList<>();
@@ -157,9 +153,10 @@ public class InternalResourceService extends SystemService {
                }
                }
            };
            };


    private static final int MSG_NOTIFY_BALANCE_CHANGE_LISTENERS = 0;
    private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
    private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
    private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
    private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
    private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";
    private static final String KEY_PKG = "pkg";


    /**
    /**
     * Initializes the system service.
     * Initializes the system service.
@@ -253,7 +250,7 @@ public class InternalResourceService extends SystemService {


    void onDeviceStateChanged() {
    void onDeviceStateChanged() {
        synchronized (mLock) {
        synchronized (mLock) {
            mAgent.updateOngoingEventsLocked();
            mAgent.onDeviceStateChangedLocked();
        }
        }
    }
    }


@@ -302,7 +299,7 @@ public class InternalResourceService extends SystemService {
            if (pkgNames == null) {
            if (pkgNames == null) {
                Slog.e(TAG, "Don't have packages for uid " + uid);
                Slog.e(TAG, "Don't have packages for uid " + uid);
            } else {
            } else {
                mAgent.updateOngoingEventsLocked(UserHandle.getUserId(uid), pkgNames);
                mAgent.onAppStatesChangedLocked(UserHandle.getUserId(uid), pkgNames);
            }
            }
        }
        }
    }
    }
@@ -331,10 +328,18 @@ public class InternalResourceService extends SystemService {
        }
        }
    }
    }


    void postSolvencyChanged(final int userId, @NonNull final String pkgName, boolean nowSolvent) {
    void postAffordabilityChanged(final int userId, @NonNull final String pkgName,
        mHandler.obtainMessage(
            @NonNull Agent.ActionAffordabilityNote affordabilityNote) {
                MSG_NOTIFY_BALANCE_CHANGE_LISTENERS, userId, nowSolvent ? 1 : 0, pkgName)
        if (DEBUG) {
                .sendToTarget();
            Slog.d(TAG, userId + ":" + pkgName + " affordability changed to "
                    + affordabilityNote.isCurrentlyAffordable());
        }
        Message msg = mHandler.obtainMessage(
                MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER, userId, 0, affordabilityNote);
        Bundle data = new Bundle();
        data.putString(KEY_PKG, pkgName);
        msg.setData(data);
        msg.sendToTarget();
    }
    }


    @GuardedBy("mLock")
    @GuardedBy("mLock")
@@ -398,45 +403,64 @@ public class InternalResourceService extends SystemService {
        @Override
        @Override
        public void handleMessage(Message msg) {
        public void handleMessage(Message msg) {
            switch (msg.what) {
            switch (msg.what) {
                case MSG_NOTIFY_BALANCE_CHANGE_LISTENERS:
                case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: {
                    Bundle data = msg.getData();
                    final int userId = msg.arg1;
                    final int userId = msg.arg1;
                    final String pkgName = (String) msg.obj;
                    final String pkgName = data.getString(KEY_PKG);
                    final boolean nowSolvent = msg.arg2 == 1;
                    final Agent.ActionAffordabilityNote affordabilityNote =
                    for (BalanceChangeListener listener : mBalanceChangeListeners) {
                            (Agent.ActionAffordabilityNote) msg.obj;
                        if (nowSolvent) {
                    final EconomyManagerInternal.AffordabilityChangeListener listener =
                            listener.onSolvent(userId, pkgName);
                            affordabilityNote.getListener();
                        } else {
                    listener.onAffordabilityChanged(userId, pkgName,
                            listener.onBankruptcy(userId, pkgName);
                            affordabilityNote.getActionBill(),
                        }
                            affordabilityNote.isCurrentlyAffordable());
                }
                }
                break;
                break;


                case MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT:
                case MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT: {
                    removeMessages(MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT);
                    removeMessages(MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT);
                    synchronized (mLock) {
                    synchronized (mLock) {
                        scheduleUnusedWealthReclamationLocked();
                        scheduleUnusedWealthReclamationLocked();
                    }
                    }
                }
                break;
                break;
            }
            }
        }
        }
    }
    }


    // TODO: implement
    private final class LocalService implements EconomyManagerInternal {
    private final class LocalService implements EconomyManagerInternal {

        @Override
        @Override
        public void registerBalanceChangeListener(@NonNull BalanceChangeListener listener) {
        public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName,
            mBalanceChangeListeners.add(listener);
                @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
            synchronized (mLock) {
                mAgent.registerAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
            }
        }
        }


        @Override
        @Override
        public void unregisterBalanceChangeListener(@NonNull BalanceChangeListener listener) {
        public void unregisterAffordabilityChangeListener(int userId, @NonNull String pkgName,
            mBalanceChangeListeners.remove(listener);
                @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) {
            synchronized (mLock) {
                mAgent.unregisterAffordabilityChangeListenerLocked(userId, pkgName, listener, bill);
            }
        }
        }


        @Override
        @Override
        public boolean hasBalanceAtLeast(int userId, @NonNull String pkgName, int minBalance) {
        public boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill) {
            return false;
            // TODO: take temp-allowlist into consideration
            long requiredBalance = 0;
            final List<EconomyManagerInternal.AnticipatedAction> projectedActions =
                    bill.getAnticipatedActions();
            for (int i = 0; i < projectedActions.size(); ++i) {
                AnticipatedAction action = projectedActions.get(i);
                final long cost =
                        mCompleteEconomicPolicy.getCostOfAction(action.actionId, userId, pkgName);
                requiredBalance += cost * action.numInstantaneousCalls
                        + cost * (action.ongoingDurationMs / 1000);
            }
            synchronized (mLock) {
                return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance;
            }
        }
        }


        @Override
        @Override
+345 −0

File added.

Preview size limit exceeded, changes collapsed.