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

Commit 47b2ba81 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Reduce individual quota alarms set." into rvc-dev am: 3201a3aa

Change-Id: If6522f58eeb4c6f2c32296e192fde5bce31a089a
parents fb9b516f 3201a3aa
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -3034,7 +3034,7 @@ public class JobSchedulerService extends com.android.server.SystemService
    }

    void resetExecutionQuota(@NonNull String pkgName, int userId) {
        mQuotaController.clearAppStats(pkgName, userId);
        mQuotaController.clearAppStats(userId, pkgName);
    }

    void resetScheduleQuota() {
+162 −100
Original line number Diff line number Diff line
@@ -54,12 +54,14 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
@@ -74,6 +76,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.function.Consumer;
import java.util.function.Predicate;

@@ -301,10 +304,10 @@ public final class QuotaController extends StateController {
    private final SparseArrayMap<List<TimingSession>> mTimingSessions = new SparseArrayMap<>();

    /**
     * List of alarm listeners for each package that listen for when each package comes back within
     * quota.
     * Listener to track and manage when each package comes back within quota.
     */
    private final SparseArrayMap<QcAlarmListener> mInQuotaAlarmListeners = new SparseArrayMap<>();
    @GuardedBy("mLock")
    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();

    /** Cached calculation results for each app, with the standby buckets as the array indices. */
    private final SparseArrayMap<ExecutionStats[]> mExecutionStatsCache = new SparseArrayMap<>();
@@ -579,7 +582,7 @@ public final class QuotaController extends StateController {
            Slog.wtf(TAG, "Told app removed but given null package name.");
            return;
        }
        clearAppStats(packageName, UserHandle.getUserId(uid));
        clearAppStats(UserHandle.getUserId(uid), packageName);
        mForegroundUids.delete(uid);
        mUidToPackageCache.remove(uid);
    }
@@ -589,13 +592,13 @@ public final class QuotaController extends StateController {
        mTrackedJobs.delete(userId);
        mPkgTimers.delete(userId);
        mTimingSessions.delete(userId);
        mInQuotaAlarmListeners.delete(userId);
        mInQuotaAlarmListener.removeAlarmsLocked(userId);
        mExecutionStatsCache.delete(userId);
        mUidToPackageCache.clear();
    }

    /** Drop all historical stats and stop tracking any active sessions for the specified app. */
    public void clearAppStats(@NonNull String packageName, int userId) {
    public void clearAppStats(int userId, @NonNull String packageName) {
        mTrackedJobs.delete(userId, packageName);
        Timer timer = mPkgTimers.get(userId, packageName);
        if (timer != null) {
@@ -606,11 +609,7 @@ public final class QuotaController extends StateController {
            mPkgTimers.delete(userId, packageName);
        }
        mTimingSessions.delete(userId, packageName);
        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
        if (alarmListener != null) {
            mAlarmManager.cancel(alarmListener);
            mInQuotaAlarmListeners.delete(userId, packageName);
        }
        mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
        mExecutionStatsCache.delete(userId, packageName);
    }

@@ -1208,12 +1207,7 @@ public final class QuotaController extends StateController {
            // exempted.
            maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
        } else {
            QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
            if (alarmListener != null && alarmListener.isWaiting()) {
                mAlarmManager.cancel(alarmListener);
                // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
                alarmListener.setTriggerTime(0);
            }
            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
        }
        return changed;
    }
@@ -1229,12 +1223,7 @@ public final class QuotaController extends StateController {
            final String packageName = jobStatus.getSourcePackageName();
            final int realStandbyBucket = jobStatus.getStandbyBucket();
            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
                QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
                if (alarmListener != null && alarmListener.isWaiting()) {
                    mAlarmManager.cancel(alarmListener);
                    // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
                    alarmListener.setTriggerTime(0);
                }
                mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
            } else {
                mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
            }
@@ -1285,7 +1274,6 @@ public final class QuotaController extends StateController {
        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
                standbyBucket);

        QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName);
        if (stats.executionTimeInWindowMs < mAllowedTimePerPeriodMs
                && stats.executionTimeInMaxPeriodMs < mMaxExecutionTimeMs
                && isUnderJobCountQuota
@@ -1297,21 +1285,11 @@ public final class QuotaController extends StateController {
                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                        + "ms in its quota.");
            }
            if (alarmListener != null) {
                // Cancel any pending alarm.
                mAlarmManager.cancel(alarmListener);
                // Set the trigger time to 0 so that the alarm doesn't think it's still waiting.
                alarmListener.setTriggerTime(0);
            }
            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
            return;
        }

        if (alarmListener == null) {
            alarmListener = new QcAlarmListener(userId, packageName);
            mInQuotaAlarmListeners.add(userId, packageName, alarmListener);
        }

        // The time this app will have quota again.
        long inQuotaTimeElapsed = stats.inQuotaTimeElapsed;
        if (!isUnderJobCountQuota && stats.bgJobCountInWindow < stats.jobCountLimit) {
@@ -1325,27 +1303,7 @@ public final class QuotaController extends StateController {
            inQuotaTimeElapsed = Math.max(inQuotaTimeElapsed,
                    stats.sessionRateLimitExpirationTimeElapsed);
        }
        // Only schedule the alarm if:
        // 1. There isn't one currently scheduled
        // 2. The new alarm is significantly earlier than the previous alarm (which could be the
        // case if the package moves into a higher standby bucket). If it's earlier but not
        // significantly so, then we essentially delay the job a few extra minutes.
        // 3. The alarm is after the current alarm by more than the quota buffer.
        // TODO: this might be overengineering. Simplify if proven safe.
        if (!alarmListener.isWaiting()
                || inQuotaTimeElapsed < alarmListener.getTriggerTimeElapsed() - 3 * MINUTE_IN_MILLIS
                || alarmListener.getTriggerTimeElapsed() < inQuotaTimeElapsed) {
            if (DEBUG) {
                Slog.d(TAG, "Scheduling start alarm for " + pkgString);
            }
            // If the next time this app will have quota is at least 3 minutes before the
            // alarm is supposed to go off, reschedule the alarm.
            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, inQuotaTimeElapsed,
                    ALARM_TAG_QUOTA_CHECK, alarmListener, mHandler);
            alarmListener.setTriggerTime(inQuotaTimeElapsed);
        } else if (DEBUG) {
            Slog.d(TAG, "No need to schedule start alarm for " + pkgString);
        }
        mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
    }

    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, boolean isWithinQuota) {
@@ -1875,32 +1833,161 @@ public final class QuotaController extends StateController {
        }
    }

    private class QcAlarmListener implements AlarmManager.OnAlarmListener {
        private final int mUserId;
        private final String mPackageName;
        private volatile long mTriggerTimeElapsed;
    static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
        AlarmQueue() {
            super(1, (o1, o2) -> (int) (o1.second - o2.second));
        }

        QcAlarmListener(int userId, String packageName) {
            mUserId = userId;
            mPackageName = packageName;
        /**
         * Remove any instances of the Package from the queue.
         *
         * @return true if an instance was removed, false otherwise.
         */
        boolean remove(@NonNull Package pkg) {
            boolean removed = false;
            Pair[] alarms = toArray(new Pair[size()]);
            for (int i = alarms.length - 1; i >= 0; --i) {
                if (pkg.equals(alarms[i].first)) {
                    remove(alarms[i]);
                    removed = true;
                }
            }
            return removed;
        }
    }

    /** Track when UPTCs are expected to come back into quota. */
    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
        @GuardedBy("mLock")
        private final AlarmQueue mAlarmQueue = new AlarmQueue();
        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
        @GuardedBy("mLock")
        private long mTriggerTimeElapsed = 0;

        boolean isWaiting() {
            return mTriggerTimeElapsed > 0;
        @GuardedBy("mLock")
        void addAlarmLocked(int userId, @NonNull String pkgName, long inQuotaTimeElapsed) {
            final Package pkg = new Package(userId, pkgName);
            mAlarmQueue.remove(pkg);
            mAlarmQueue.offer(new Pair<>(pkg, inQuotaTimeElapsed));
            setNextAlarmLocked();
        }

        void setTriggerTime(long timeElapsed) {
            mTriggerTimeElapsed = timeElapsed;
        @GuardedBy("mLock")
        void removeAlarmLocked(@NonNull Package pkg) {
            if (mAlarmQueue.remove(pkg)) {
                setNextAlarmLocked();
            }
        }

        long getTriggerTimeElapsed() {
            return mTriggerTimeElapsed;
        @GuardedBy("mLock")
        void removeAlarmLocked(int userId, @NonNull String packageName) {
            removeAlarmLocked(new Package(userId, packageName));
        }

        @GuardedBy("mLock")
        void removeAlarmsLocked(int userId) {
            boolean removed = false;
            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
            for (int i = alarms.length - 1; i >= 0; --i) {
                final Package pkg = (Package) alarms[i].first;
                if (userId == pkg.userId) {
                    mAlarmQueue.remove(alarms[i]);
                    removed = true;
                }
            }
            if (removed) {
                setNextAlarmLocked();
            }
        }

        @GuardedBy("mLock")
        private void setNextAlarmLocked() {
            if (mAlarmQueue.size() > 0) {
                final long nextTriggerTimeElapsed = mAlarmQueue.peek().second;
                // Only schedule the alarm if one of the following is true:
                // 1. There isn't one currently scheduled
                // 2. The new alarm is significantly earlier than the previous alarm. If it's
                // earlier but not significantly so, then we essentially delay the job a few extra
                // minutes.
                // 3. The alarm is after the current alarm.
                if (mTriggerTimeElapsed == 0
                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
                    if (DEBUG) {
                        Slog.d(TAG, "Scheduling start alarm at " + nextTriggerTimeElapsed);
                    }
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
                            ALARM_TAG_QUOTA_CHECK, this, mHandler);
                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
                }
            } else {
                mAlarmManager.cancel(this);
                mTriggerTimeElapsed = 0;
            }
        }

        @Override
        public void onAlarm() {
            mHandler.obtainMessage(MSG_CHECK_PACKAGE, mUserId, 0, mPackageName).sendToTarget();
            mTriggerTimeElapsed = 0;
            synchronized (mLock) {
                while (mAlarmQueue.size() > 0) {
                    final Pair<Package, Long> alarm = mAlarmQueue.peek();
                    if (alarm.second <= sElapsedRealtimeClock.millis()) {
                        mHandler.obtainMessage(MSG_CHECK_PACKAGE, alarm.first.userId, 0,
                                alarm.first.packageName).sendToTarget();
                        mAlarmQueue.remove(alarm);
                    } else {
                        break;
                    }
                }
                setNextAlarmLocked();
            }
        }

        @GuardedBy("mLock")
        void dumpLocked(IndentingPrintWriter pw) {
            pw.println("In quota alarms:");
            pw.increaseIndent();

            if (mAlarmQueue.size() == 0) {
                pw.println("NOT WAITING");
            } else {
                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
                for (int i = 0; i < alarms.length; ++i) {
                    final Package pkg = (Package) alarms[i].first;
                    pw.print(pkg);
                    pw.print(": ");
                    pw.print(alarms[i].second);
                    pw.println();
                }
            }

            pw.decreaseIndent();
        }

        @GuardedBy("mLock")
        void dumpLocked(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);

            proto.write(
                    StateControllerProto.QuotaController.InQuotaAlarmListener.TRIGGER_TIME_ELAPSED,
                    mTriggerTimeElapsed);

            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
            for (int i = 0; i < alarms.length; ++i) {
                final long aToken = proto.start(
                        StateControllerProto.QuotaController.InQuotaAlarmListener.ALARMS);

                final Package pkg = (Package) alarms[i].first;
                pkg.dumpDebug(proto,
                        StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.PKG);
                proto.write(
                        StateControllerProto.QuotaController.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
                        (Long) alarms[i].second);

                proto.end(aToken);
            }

            proto.end(token);
        }
    }

@@ -2618,23 +2705,7 @@ public final class QuotaController extends StateController {
        pw.decreaseIndent();

        pw.println();
        pw.println("In quota alarms:");
        pw.increaseIndent();
        for (int u = 0; u < mInQuotaAlarmListeners.numMaps(); ++u) {
            final int userId = mInQuotaAlarmListeners.keyAt(u);
            for (int p = 0; p < mInQuotaAlarmListeners.numElementsForKey(userId); ++p) {
                final String pkgName = mInQuotaAlarmListeners.keyAt(u, p);
                QcAlarmListener alarmListener = mInQuotaAlarmListeners.valueAt(u, p);

                pw.print(string(userId, pkgName));
                pw.print(": ");
                if (alarmListener.isWaiting()) {
                    pw.println(alarmListener.getTriggerTimeElapsed());
                } else {
                    pw.println("NOT WAITING");
                }
            }
        }
        mInQuotaAlarmListener.dumpLocked(pw);
        pw.decreaseIndent();
    }

@@ -2768,22 +2839,13 @@ public final class QuotaController extends StateController {
                    }
                }

                QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, pkgName);
                if (alarmListener != null) {
                    final long alToken = proto.start(
                            StateControllerProto.QuotaController.PackageStats.IN_QUOTA_ALARM_LISTENER);
                    proto.write(StateControllerProto.QuotaController.AlarmListener.IS_WAITING,
                            alarmListener.isWaiting());
                    proto.write(
                            StateControllerProto.QuotaController.AlarmListener.TRIGGER_TIME_ELAPSED,
                            alarmListener.getTriggerTimeElapsed());
                    proto.end(alToken);
                }

                proto.end(psToken);
            }
        }

        mInQuotaAlarmListener.dumpLocked(proto,
                StateControllerProto.QuotaController.IN_QUOTA_ALARM_LISTENER);

        proto.end(mToken);
        proto.end(token);
    }
+20 −2
Original line number Diff line number Diff line
@@ -670,7 +670,7 @@ message StateControllerProto {

            repeated ExecutionStats execution_stats = 4;

            optional AlarmListener in_quota_alarm_listener = 5;
            reserved 5; // in_quota_alarm_listener
        }
        repeated PackageStats package_stats = 5;

@@ -683,7 +683,25 @@ message StateControllerProto {
        }
        repeated UidPackageMapping uid_to_package_cache = 7;

        // Next tag: 8
        message InQuotaAlarmListener {
            option (.android.msg_privacy).dest = DEST_AUTOMATIC;

            // The time at which the alarm is set to go off, in the elapsed realtime timebase.
            optional int64 trigger_time_elapsed = 1;

            message Alarm {
                option (.android.msg_privacy).dest = DEST_AUTOMATIC;

                optional Package pkg = 1;

                // The time at which the package will be in quota, in the elapsed realtime timebase.
                optional int64 in_quota_time_elapsed = 2;
            }
            repeated Alarm alarms = 2;
        }
        optional InQuotaAlarmListener in_quota_alarm_listener = 8;

        // Next tag: 9
    }
    message StorageController {
        option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+1 −0
Original line number Diff line number Diff line
@@ -552,6 +552,7 @@ abstract class QuotaTracker {
                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
                }
            } else {
                cancelAlarm(this);
                mTriggerTimeElapsed = 0;
            }
        }