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

Commit a64e3644 authored by Kweku Adams's avatar Kweku Adams
Browse files

Extract AlarmQueue into its own class.

The AlarmQueue concept is used in several areas, so extracting and
centralizing the code to reduce code duplication.

Bug: 141645789
Bug: 194532703
Test: atest CtsJobSchedulerTestCases
Test: atest FrameworksMockingServicesTests:AgentTest
Test: atest FrameworksMockingServicesTests:AlarmQueueTest
Test: atest FrameworksMockingServicesTests:CountQuotaTrackerTest
Test: atest FrameworksMockingServicesTests:MultiRateLimiterTest
Test: atest FrameworksMockingServicesTests:QuotaControllerTest
Change-Id: Ic070ce3c786de32031cc196578f91086bac8121d
parent 56244e07
Loading
Loading
Loading
Loading
+26 −180
Original line number Original line Diff line number Diff line
@@ -56,7 +56,6 @@ import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.SparseArrayMap;
@@ -76,11 +75,11 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
import com.android.server.job.StateControllerProto;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import com.android.server.utils.AlarmQueue;


import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.function.Consumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Predicate;


@@ -323,10 +322,10 @@ public final class QuotaController extends StateController {
            new SparseArrayMap<>();
            new SparseArrayMap<>();


    /**
    /**
     * Listener to track and manage when each package comes back within quota.
     * Queue to track and manage when each package comes back within quota.
     */
     */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
    private final InQuotaAlarmQueue mInQuotaAlarmQueue;


    /** Cached calculation results for each app, with the standby buckets as the array indices. */
    /** Cached calculation results for each app, with the standby buckets as the array indices. */
    private final SparseArrayMap<String, ExecutionStats[]> mExecutionStatsCache =
    private final SparseArrayMap<String, ExecutionStats[]> mExecutionStatsCache =
@@ -589,11 +588,12 @@ public final class QuotaController extends StateController {
        mHandler = new QcHandler(mContext.getMainLooper());
        mHandler = new QcHandler(mContext.getMainLooper());
        mChargeTracker = new ChargingTracker();
        mChargeTracker = new ChargingTracker();
        mChargeTracker.startTracking();
        mChargeTracker.startTracking();
        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        mAlarmManager = mContext.getSystemService(AlarmManager.class);
        mQcConstants = new QcConstants();
        mQcConstants = new QcConstants();
        mBackgroundJobsController = backgroundJobsController;
        mBackgroundJobsController = backgroundJobsController;
        mConnectivityController = connectivityController;
        mConnectivityController = connectivityController;
        mIsEnabled = !mConstants.USE_TARE_POLICY;
        mIsEnabled = !mConstants.USE_TARE_POLICY;
        mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper());


        // Set up the app standby bucketing tracker
        // Set up the app standby bucketing tracker
        AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
        AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
@@ -742,7 +742,7 @@ public final class QuotaController extends StateController {
        mEJPkgTimers.delete(userId);
        mEJPkgTimers.delete(userId);
        mTimingSessions.delete(userId);
        mTimingSessions.delete(userId);
        mEJTimingSessions.delete(userId);
        mEJTimingSessions.delete(userId);
        mInQuotaAlarmListener.removeAlarmsLocked(userId);
        mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
        mExecutionStatsCache.delete(userId);
        mExecutionStatsCache.delete(userId);
        mEJStats.delete(userId);
        mEJStats.delete(userId);
        mSystemInstallers.remove(userId);
        mSystemInstallers.remove(userId);
@@ -768,7 +768,7 @@ public final class QuotaController extends StateController {
        }
        }
        mTimingSessions.delete(userId, packageName);
        mTimingSessions.delete(userId, packageName);
        mEJTimingSessions.delete(userId, packageName);
        mEJTimingSessions.delete(userId, packageName);
        mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
        mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
        mExecutionStatsCache.delete(userId, packageName);
        mExecutionStatsCache.delete(userId, packageName);
        mEJStats.delete(userId, packageName);
        mEJStats.delete(userId, packageName);
        mTopAppTrackers.delete(userId, packageName);
        mTopAppTrackers.delete(userId, packageName);
@@ -1657,7 +1657,7 @@ public final class QuotaController extends StateController {
            // exempted.
            // exempted.
            maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
            maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
        } else {
        } else {
            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
        }
        }
        return changed;
        return changed;
    }
    }
@@ -1695,7 +1695,7 @@ public final class QuotaController extends StateController {
            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
            if (isWithinQuotaLocked(userId, packageName, realStandbyBucket) && isWithinEJQuota) {
                // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
                // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
                // that all jobs for the userId-package are within quota.
                // that all jobs for the userId-package are within quota.
                mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
            } else {
            } else {
                mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
                mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
            }
            }
@@ -1760,7 +1760,7 @@ public final class QuotaController extends StateController {
                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                        + "ms in its quota.");
                        + "ms in its quota.");
            }
            }
            mInQuotaAlarmListener.removeAlarmLocked(userId, packageName);
            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
            mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
            return;
            return;
        }
        }
@@ -1825,7 +1825,7 @@ public final class QuotaController extends StateController {
                            + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
                            + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
            inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
            inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
        }
        }
        mInQuotaAlarmListener.addAlarmLocked(userId, packageName, inQuotaTimeElapsed);
        mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed);
    }
    }


    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
    private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
@@ -2805,176 +2805,25 @@ public final class QuotaController extends StateController {
        }
        }
    }
    }


    static class AlarmQueue extends PriorityQueue<Pair<Package, Long>> {
        AlarmQueue() {
            super(1, (o1, o2) -> (int) (o1.second - o2.second));
        }

        /**
         * 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. */
    /** Track when UPTCs are expected to come back into quota. */
    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
    private class InQuotaAlarmQueue extends AlarmQueue<Package> {
        @GuardedBy("mLock")
        private InQuotaAlarmQueue(Context context, Looper looper) {
        private final AlarmQueue mAlarmQueue = new AlarmQueue();
            super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false,
        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
                    QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
        @GuardedBy("mLock")
        private long mTriggerTimeElapsed = 0;
        /** The minimum amount of time between quota check alarms. */
        @GuardedBy("mLock")
        private long mMinQuotaCheckDelayMs = QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS;

        @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();
        }

        @GuardedBy("mLock")
        void setMinQuotaCheckDelayMs(long minDelayMs) {
            mMinQuotaCheckDelayMs = minDelayMs;
        }

        @GuardedBy("mLock")
        void removeAlarmLocked(@NonNull Package pkg) {
            if (mAlarmQueue.remove(pkg)) {
                setNextAlarmLocked();
            }
        }

        @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() {
            setNextAlarmLocked(sElapsedRealtimeClock.millis());
        }

        @GuardedBy("mLock")
        private void setNextAlarmLocked(long earliestTriggerElapsed) {
            if (mAlarmQueue.size() > 0) {
                final Pair<Package, Long> alarm = mAlarmQueue.peek();
                final long nextTriggerTimeElapsed = Math.max(earliestTriggerElapsed, alarm.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
                                + " for app " + alarm.first);
                    }
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
                            ALARM_TAG_QUOTA_CHECK, this, mHandler);
                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
                }
            } else {
                mAlarmManager.cancel(this);
                mTriggerTimeElapsed = 0;
            }
        }
        }


        @Override
        @Override
        public void onAlarm() {
        protected boolean isForUser(@NonNull Package key, int userId) {
            synchronized (mLock) {
            return key.userId == userId;
                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(sElapsedRealtimeClock.millis() + mMinQuotaCheckDelayMs);
            }
        }
        }


        @GuardedBy("mLock")
        @Override
        void dumpLocked(IndentingPrintWriter pw) {
        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
            pw.println("In quota alarms:");
            for (int i = 0; i < expired.size(); ++i) {
            pw.increaseIndent();
                Package p = expired.valueAt(i);

                mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName)
            if (mAlarmQueue.size() == 0) {
                        .sendToTarget();
                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);
        }
        }
    }
    }


@@ -3563,7 +3412,7 @@ public final class QuotaController extends StateController {
                            properties.getLong(key, DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
                            properties.getLong(key, DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
                    // We don't need to re-evaluate execution stats or constraint status for this.
                    // We don't need to re-evaluate execution stats or constraint status for this.
                    // Limit the delay to the range [0, 15] minutes.
                    // Limit the delay to the range [0, 15] minutes.
                    mInQuotaAlarmListener.setMinQuotaCheckDelayMs(
                    mInQuotaAlarmQueue.setMinTimeBetweenAlarmsMs(
                            Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
                            Math.min(15 * MINUTE_IN_MILLIS, Math.max(0, MIN_QUOTA_CHECK_DELAY_MS)));
                    break;
                    break;
                case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS:
                case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS:
@@ -4104,7 +3953,7 @@ public final class QuotaController extends StateController {


    @VisibleForTesting
    @VisibleForTesting
    long getMinQuotaCheckDelayMs() {
    long getMinQuotaCheckDelayMs() {
        return mInQuotaAlarmListener.mMinQuotaCheckDelayMs;
        return mInQuotaAlarmQueue.getMinTimeBetweenAlarmsMs();
    }
    }


    @VisibleForTesting
    @VisibleForTesting
@@ -4302,7 +4151,7 @@ public final class QuotaController extends StateController {
        pw.decreaseIndent();
        pw.decreaseIndent();


        pw.println();
        pw.println();
        mInQuotaAlarmListener.dumpLocked(pw);
        mInQuotaAlarmQueue.dump(pw);
        pw.decreaseIndent();
        pw.decreaseIndent();
    }
    }


@@ -4438,9 +4287,6 @@ public final class QuotaController extends StateController {
            }
            }
        }
        }


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

        proto.end(mToken);
        proto.end(mToken);
        proto.end(token);
        proto.end(token);
    }
    }
+49 −259

File changed.

Preview size limit exceeded, changes collapsed.

+366 −0

File added.

Preview size limit exceeded, changes collapsed.

+26 −173
Original line number Original line Diff line number Diff line
@@ -29,11 +29,11 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter;
import android.net.Uri;
import android.net.Uri;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoOutputStream;
@@ -45,8 +45,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.server.FgThread;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.SystemServiceManager;

import com.android.server.utils.AlarmQueue;
import java.util.PriorityQueue;


/**
/**
 * Base class for trackers that track whether an app has exceeded a count quota.
 * Base class for trackers that track whether an app has exceeded a count quota.
@@ -88,10 +87,10 @@ abstract class QuotaTracker {
    private final ArraySet<QuotaChangeListener> mQuotaChangeListeners = new ArraySet<>();
    private final ArraySet<QuotaChangeListener> mQuotaChangeListeners = new ArraySet<>();


    /**
    /**
     * Listener to track and manage when each package comes back within quota.
     * Alarm queue to track and manage when each package comes back within quota.
     */
     */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final InQuotaAlarmListener mInQuotaAlarmListener = new InQuotaAlarmListener();
    private final InQuotaAlarmQueue mInQuotaAlarmQueue;


    /** "Free quota status" for apps. */
    /** "Free quota status" for apps. */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
@@ -163,6 +162,8 @@ abstract class QuotaTracker {
        mContext = context;
        mContext = context;
        mInjector = injector;
        mInjector = injector;
        mAlarmManager = mContext.getSystemService(AlarmManager.class);
        mAlarmManager = mContext.getSystemService(AlarmManager.class);
        // The operation should be fast enough to put it on the FgThread.
        mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, FgThread.getHandler().getLooper());


        final IntentFilter filter = new IntentFilter();
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
@@ -179,7 +180,7 @@ abstract class QuotaTracker {
    /** Remove all saved events from the tracker. */
    /** Remove all saved events from the tracker. */
    public void clear() {
    public void clear() {
        synchronized (mLock) {
        synchronized (mLock) {
            mInQuotaAlarmListener.clearLocked();
            mInQuotaAlarmQueue.removeAllAlarms();
            mFreeQuota.clear();
            mFreeQuota.clear();


            dropEverythingLocked();
            dropEverythingLocked();
@@ -367,7 +368,7 @@ abstract class QuotaTracker {
            return;
            return;
        }
        }


        mInQuotaAlarmListener.removeAlarmsLocked(userId, packageName);
        mInQuotaAlarmQueue.removeAlarms(userId, packageName);


        mFreeQuota.delete(userId, packageName);
        mFreeQuota.delete(userId, packageName);


@@ -379,7 +380,7 @@ abstract class QuotaTracker {


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private void onUserRemovedLocked(int userId) {
    private void onUserRemovedLocked(int userId) {
        mInQuotaAlarmListener.removeAlarmsLocked(userId);
        mInQuotaAlarmQueue.removeAlarmsForUserId(userId);
        mFreeQuota.delete(userId);
        mFreeQuota.delete(userId);


        handleRemovedUserLocked(userId);
        handleRemovedUserLocked(userId);
@@ -434,190 +435,43 @@ abstract class QuotaTracker {
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
                        + " even though it's within quota");
                        + " even though it's within quota");
            }
            }
            mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
            mInQuotaAlarmQueue.removeAlarmForKey(new Uptc(userId, packageName, tag));
            maybeUpdateQuotaStatus(userId, packageName, tag);
            maybeUpdateQuotaStatus(userId, packageName, tag);
            return;
            return;
        }
        }


        mInQuotaAlarmListener.addAlarmLocked(new Uptc(userId, packageName, tag),
        mInQuotaAlarmQueue.addAlarm(new Uptc(userId, packageName, tag),
                getInQuotaTimeElapsedLocked(userId, packageName, tag));
                getInQuotaTimeElapsedLocked(userId, packageName, tag));
    }
    }


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    void cancelScheduledStartAlarmLocked(final int userId,
    void cancelScheduledStartAlarmLocked(final int userId,
            @NonNull final String packageName, @Nullable final String tag) {
            @NonNull final String packageName, @Nullable final String tag) {
        mInQuotaAlarmListener.removeAlarmLocked(new Uptc(userId, packageName, tag));
        mInQuotaAlarmQueue.removeAlarmForKey(new Uptc(userId, packageName, tag));
    }

    static class AlarmQueue extends PriorityQueue<Pair<Uptc, Long>> {
        AlarmQueue() {
            super(1, (o1, o2) -> (int) (o1.second - o2.second));
        }

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


    /** Track when UPTCs are expected to come back into quota. */
    /** Track when UPTCs are expected to come back into quota. */
    private class InQuotaAlarmListener implements AlarmManager.OnAlarmListener {
    private class InQuotaAlarmQueue extends AlarmQueue<Uptc> {
        @GuardedBy("mLock")
        private InQuotaAlarmQueue(Context context, Looper looper) {
        private final AlarmQueue mAlarmQueue = new AlarmQueue();
            super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false, 0);
        /** The next time the alarm is set to go off, in the elapsed realtime timebase. */
        @GuardedBy("mLock")
        private long mTriggerTimeElapsed = 0;

        @GuardedBy("mLock")
        void addAlarmLocked(@NonNull Uptc uptc, long inQuotaTimeElapsed) {
            mAlarmQueue.remove(uptc);
            mAlarmQueue.offer(new Pair<>(uptc, inQuotaTimeElapsed));
            setNextAlarmLocked();
        }

        @GuardedBy("mLock")
        void clearLocked() {
            cancelAlarm(this);
            mAlarmQueue.clear();
            mTriggerTimeElapsed = 0;
        }

        @GuardedBy("mLock")
        void removeAlarmLocked(@NonNull Uptc uptc) {
            if (mAlarmQueue.remove(uptc)) {
                if (mAlarmQueue.size() == 0) {
                    cancelAlarm(this);
                } else {
                    setNextAlarmLocked();
                }
            }
        }

        @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 Uptc uptc = (Uptc) alarms[i].first;
                if (userId == uptc.userId) {
                    mAlarmQueue.remove(alarms[i]);
                    removed = true;
                }
            }
            if (removed) {
                setNextAlarmLocked();
            }
        }

        @GuardedBy("mLock")
        void removeAlarmsLocked(int userId, @NonNull String packageName) {
            boolean removed = false;
            Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
            for (int i = alarms.length - 1; i >= 0; --i) {
                final Uptc uptc = (Uptc) alarms[i].first;
                if (userId == uptc.userId && packageName.equals(uptc.packageName)) {
                    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 notification a
                // few extra minutes.
                if (mTriggerTimeElapsed == 0
                        || nextTriggerTimeElapsed < mTriggerTimeElapsed - 3 * MINUTE_IN_MILLIS
                        || mTriggerTimeElapsed < nextTriggerTimeElapsed) {
                    // Use a non-wakeup alarm for this
                    scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextTriggerTimeElapsed,
                            ALARM_TAG_QUOTA_CHECK, this);
                    mTriggerTimeElapsed = nextTriggerTimeElapsed;
                }
            } else {
                cancelAlarm(this);
                mTriggerTimeElapsed = 0;
            }
        }
        }


        @Override
        @Override
        public void onAlarm() {
        protected boolean isForUser(@NonNull Uptc uptc, int userId) {
            synchronized (mLock) {
            return userId == uptc.userId;
                while (mAlarmQueue.size() > 0) {
                    final Pair<Uptc, Long> alarm = mAlarmQueue.peek();
                    if (alarm.second <= mInjector.getElapsedRealtime()) {
                        getHandler().post(() -> maybeUpdateQuotaStatus(
                                alarm.first.userId, alarm.first.packageName, alarm.first.tag));
                        mAlarmQueue.remove(alarm);
                    } else {
                        break;
                    }
                }
                setNextAlarmLocked();
        }
        }
        }

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


            if (mAlarmQueue.size() == 0) {
        void removeAlarms(int userId, @NonNull String packageName) {
                pw.println("NOT WAITING");
            removeAlarmsIf((uptc) -> userId == uptc.userId && packageName.equals(uptc.packageName));
            } else {
                Pair[] alarms = mAlarmQueue.toArray(new Pair[mAlarmQueue.size()]);
                for (int i = 0; i < alarms.length; ++i) {
                    final Uptc uptc = (Uptc) alarms[i].first;
                    pw.print(uptc);
                    pw.print(": ");
                    pw.print(alarms[i].second);
                    pw.println();
                }
        }
        }


            pw.decreaseIndent();
        @Override
        }
        protected void processExpiredAlarms(@NonNull ArraySet<Uptc> expired) {

            for (int i = 0; i < expired.size(); ++i) {
        @GuardedBy("mLock")
                Uptc uptc = expired.valueAt(i);
        void dumpLocked(ProtoOutputStream proto, long fieldId) {
                getHandler().post(
            final long token = proto.start(fieldId);
                        () -> maybeUpdateQuotaStatus(uptc.userId, uptc.packageName, uptc.tag));

            proto.write(QuotaTrackerProto.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(QuotaTrackerProto.InQuotaAlarmListener.ALARMS);

                final Uptc uptc = (Uptc) alarms[i].first;
                uptc.dumpDebug(proto, QuotaTrackerProto.InQuotaAlarmListener.Alarm.UPTC);
                proto.write(QuotaTrackerProto.InQuotaAlarmListener.Alarm.IN_QUOTA_TIME_ELAPSED,
                        (Long) alarms[i].second);

                proto.end(aToken);
            }
            }

            proto.end(token);
        }
        }
    }
    }


@@ -635,7 +489,7 @@ abstract class QuotaTracker {
            pw.println();
            pw.println();


            pw.println();
            pw.println();
            mInQuotaAlarmListener.dumpLocked(pw);
            mInQuotaAlarmQueue.dump(pw);


            pw.println();
            pw.println();
            pw.println("Per-app free quota:");
            pw.println("Per-app free quota:");
@@ -669,7 +523,6 @@ abstract class QuotaTracker {
            proto.write(QuotaTrackerProto.IS_ENABLED, mIsEnabled);
            proto.write(QuotaTrackerProto.IS_ENABLED, mIsEnabled);
            proto.write(QuotaTrackerProto.IS_GLOBAL_QUOTA_FREE, mIsQuotaFree);
            proto.write(QuotaTrackerProto.IS_GLOBAL_QUOTA_FREE, mIsQuotaFree);
            proto.write(QuotaTrackerProto.ELAPSED_REALTIME, mInjector.getElapsedRealtime());
            proto.write(QuotaTrackerProto.ELAPSED_REALTIME, mInjector.getElapsedRealtime());
            mInQuotaAlarmListener.dumpLocked(proto, QuotaTrackerProto.IN_QUOTA_ALARM_LISTENER);
        }
        }


        proto.end(token);
        proto.end(token);
+138 −99

File changed.

Preview size limit exceeded, changes collapsed.

Loading