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

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

Merge "Add ThresholdAlarmQueue."

parents 669d4860 cd3e04b8
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -113,7 +113,6 @@ public class ComponentController extends StateController {
        userFilter.addAction(Intent.ACTION_USER_STOPPED);
        mContext.registerReceiverAsUser(
                mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);

    }

    @Override
+59 −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.job.controllers;

import java.util.Objects;

/** Wrapper class to represent a userId-pkgName combo. */
final class Package {
    public final String packageName;
    public final int userId;

    Package(int userId, String packageName) {
        this.userId = userId;
        this.packageName = packageName;
    }

    @Override
    public String toString() {
        return packageToString(userId, packageName);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Package)) {
            return false;
        }
        Package other = (Package) obj;
        return userId == other.userId && Objects.equals(packageName, other.packageName);
    }

    @Override
    public int hashCode() {
        return packageName.hashCode() + userId;
    }

    /**
     * Standardize the output of userId-packageName combo.
     */
    static String packageToString(int userId, String packageName) {
        return "<" + userId + ">" + packageName;
    }
}
+112 −13
Original line number Diff line number Diff line
@@ -20,9 +20,13 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
import static com.android.server.job.controllers.Package.packageToString;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -36,6 +40,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;

import java.util.function.Predicate;

@@ -57,6 +62,7 @@ public class PrefetchController extends StateController {
     */
    @GuardedBy("mLock")
    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
    private final ThresholdAlarmListener mThresholdAlarmListener;

    /**
     * The cutoff point to decide if a prefetch job is worth running or not. If the app is expected
@@ -69,6 +75,8 @@ public class PrefetchController extends StateController {
    public PrefetchController(JobSchedulerService service) {
        super(service);
        mPcConstants = new PcConstants();
        mThresholdAlarmListener = new ThresholdAlarmListener(
                mContext, JobSchedulerBackgroundThread.get().getLooper());
    }

    @Override
@@ -82,9 +90,13 @@ public class PrefetchController extends StateController {
                jobs = new ArraySet<>();
                mTrackedJobs.add(userId, pkgName, jobs);
            }
            jobs.add(jobStatus);
            updateConstraintLocked(jobStatus,
                    sSystemClock.millis(), sElapsedRealtimeClock.millis());
            final long now = sSystemClock.millis();
            final long nowElapsed = sElapsedRealtimeClock.millis();
            if (jobs.add(jobStatus) && jobs.size() == 1
                    && !willBeLaunchedSoonLocked(userId, pkgName, now)) {
                updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
            }
            updateConstraintLocked(jobStatus, now, nowElapsed);
        }
    }

@@ -92,10 +104,11 @@ public class PrefetchController extends StateController {
    @GuardedBy("mLock")
    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
            boolean forUpdate) {
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUserId(),
                jobStatus.getSourcePackageName());
        if (jobs != null) {
            jobs.remove(jobStatus);
        final int userId = jobStatus.getSourceUserId();
        final String pkgName = jobStatus.getSourcePackageName();
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
        if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
        }
    }

@@ -109,6 +122,7 @@ public class PrefetchController extends StateController {
        final int userId = UserHandle.getUserId(uid);
        mTrackedJobs.delete(userId, packageName);
        mEstimatedLaunchTimes.delete(userId, packageName);
        mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
    }

    @Override
@@ -116,6 +130,7 @@ public class PrefetchController extends StateController {
    public void onUserRemovedLocked(int userId) {
        mTrackedJobs.delete(userId);
        mEstimatedLaunchTimes.delete(userId);
        mThresholdAlarmListener.removeAlarmsForUserId(userId);
    }

    /** Return the app's next estimated launch time. */
@@ -124,8 +139,14 @@ public class PrefetchController extends StateController {
    public long getNextEstimatedLaunchTimeLocked(@NonNull JobStatus jobStatus) {
        final int userId = jobStatus.getSourceUserId();
        final String pkgName = jobStatus.getSourcePackageName();
        return getNextEstimatedLaunchTimeLocked(userId, pkgName, sSystemClock.millis());
    }

    @GuardedBy("mLock")
    @CurrentTimeMillisLong
    private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
            @CurrentTimeMillisLong long now) {
        Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
        final long now = sSystemClock.millis();
        if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
            // TODO(194532703): get estimated time from UsageStats
            nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
@@ -135,8 +156,8 @@ public class PrefetchController extends StateController {
    }

    @GuardedBy("mLock")
    private boolean maybeUpdateConstraintForPkgLocked(long now, long nowElapsed, int userId,
            String pkgName) {
    private boolean maybeUpdateConstraintForPkgLocked(@CurrentTimeMillisLong long now,
            @ElapsedRealtimeLong long nowElapsed, int userId, String pkgName) {
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
        if (jobs == null) {
            return false;
@@ -150,10 +171,43 @@ public class PrefetchController extends StateController {
    }

    @GuardedBy("mLock")
    private boolean updateConstraintLocked(@NonNull JobStatus jobStatus, long now,
            long nowElapsed) {
    private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
            @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
        return jobStatus.setPrefetchConstraintSatisfied(nowElapsed,
                getNextEstimatedLaunchTimeLocked(jobStatus) <= now + mLaunchTimeThresholdMs);
                willBeLaunchedSoonLocked(
                        jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), now));
    }

    @GuardedBy("mLock")
    private void updateThresholdAlarmLocked(int userId, @NonNull String pkgName,
            @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
        final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
        if (jobs == null || jobs.size() == 0) {
            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
            return;
        }

        final long nextEstimatedLaunchTime = getNextEstimatedLaunchTimeLocked(userId, pkgName, now);
        if (nextEstimatedLaunchTime - now > mLaunchTimeThresholdMs) {
            // Set alarm to be notified when this crosses the threshold.
            final long timeToCrossThresholdMs =
                    nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
            mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
                    nowElapsed + timeToCrossThresholdMs);
        } else {
            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
        }
    }

    /**
     * Returns true if the app is expected to be launched soon, where "soon" is within the next
     * {@link #mLaunchTimeThresholdMs} time.
     */
    @GuardedBy("mLock")
    private boolean willBeLaunchedSoonLocked(int userId, @NonNull String pkgName,
            @CurrentTimeMillisLong long now) {
        return getNextEstimatedLaunchTimeLocked(userId, pkgName, now)
                <= now + mLaunchTimeThresholdMs;
    }

    @Override
@@ -186,6 +240,9 @@ public class PrefetchController extends StateController {
                                    now, nowElapsed, userId, packageName)) {
                                changedJobs.addAll(mTrackedJobs.valueAt(u, p));
                            }
                            if (!willBeLaunchedSoonLocked(userId, packageName, now)) {
                                updateThresholdAlarmLocked(userId, packageName, now, nowElapsed);
                            }
                        }
                    }
                }
@@ -196,6 +253,42 @@ public class PrefetchController extends StateController {
        }
    }

    /** Track when apps will cross the "will run soon" threshold. */
    private class ThresholdAlarmListener extends AlarmQueue<Package> {
        private ThresholdAlarmListener(Context context, Looper looper) {
            super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
                    PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
        }

        @Override
        protected boolean isForUser(@NonNull Package key, int userId) {
            return key.userId == userId;
        }

        @Override
        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
            final ArraySet<JobStatus> changedJobs = new ArraySet<>();
            synchronized (mLock) {
                final long now = sSystemClock.millis();
                final long nowElapsed = sElapsedRealtimeClock.millis();
                for (int i = 0; i < expired.size(); ++i) {
                    Package p = expired.valueAt(i);
                    if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
                        Slog.e(TAG, "Alarm expired for "
                                + packageToString(p.userId, p.packageName) + " at the wrong time");
                        updateThresholdAlarmLocked(p.userId, p.packageName, now, nowElapsed);
                    } else if (maybeUpdateConstraintForPkgLocked(
                            now, nowElapsed, p.userId, p.packageName)) {
                        changedJobs.addAll(mTrackedJobs.get(p.userId, p.packageName));
                    }
                }
            }
            if (changedJobs.size() > 0) {
                mStateChangedListener.onControllerStateChanged(changedJobs);
            }
        }
    }

    @VisibleForTesting
    class PcConstants {
        private boolean mShouldReevaluateConstraints = false;
@@ -225,6 +318,9 @@ public class PrefetchController extends StateController {
                    if (mLaunchTimeThresholdMs != newLaunchTimeThresholdMs) {
                        mLaunchTimeThresholdMs = newLaunchTimeThresholdMs;
                        mShouldReevaluateConstraints = true;
                        // Give a leeway of 10% of the launch time threshold between alarms.
                        mThresholdAlarmListener.setMinTimeBetweenAlarmsMs(
                                mLaunchTimeThresholdMs / 10);
                    }
                    break;
            }
@@ -294,6 +390,9 @@ public class PrefetchController extends StateController {
                pw.println();
            }
        });

        pw.println();
        mThresholdAlarmListener.dump(pw);
    }

    @Override
+10 −58
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.Package.packageToString;

import android.Manifest;
import android.annotation.NonNull;
@@ -79,7 +80,6 @@ import com.android.server.utils.AlarmQueue;

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

@@ -123,52 +123,6 @@ public final class QuotaController extends StateController {
            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                    | PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;

    /**
     * Standardize the output of userId-packageName combo.
     */
    private static String string(int userId, String packageName) {
        return "<" + userId + ">" + packageName;
    }

    private static final class Package {
        public final String packageName;
        public final int userId;

        Package(int userId, String packageName) {
            this.userId = userId;
            this.packageName = packageName;
        }

        @Override
        public String toString() {
            return string(userId, packageName);
        }

        public void dumpDebug(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);

            proto.write(StateControllerProto.QuotaController.Package.USER_ID, userId);
            proto.write(StateControllerProto.QuotaController.Package.NAME, packageName);

            proto.end(token);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Package) {
                Package other = (Package) obj;
                return userId == other.userId && Objects.equals(packageName, other.packageName);
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return packageName.hashCode() + userId;
        }
    }

    private static int hashLong(long val) {
        return (int) (val ^ (val >>> 32));
    }
@@ -1741,7 +1695,6 @@ public final class QuotaController extends StateController {
            return;
        }

        final String pkgString = string(userId, packageName);
        ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket);
        final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket);
        final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats,
@@ -1755,7 +1708,8 @@ public final class QuotaController extends StateController {
        if (inRegularQuota && remainingEJQuota > 0) {
            // Already in quota. Why was this method called?
            if (DEBUG) {
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + pkgString
                Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
                        + packageToString(userId, packageName)
                        + " even though it already has "
                        + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                        + "ms in its quota.");
@@ -1811,8 +1765,8 @@ public final class QuotaController extends StateController {
                // In some strange cases, an app may end be in the NEVER bucket but could have run
                // some regular jobs. This results in no EJ timing sessions and QC having a bad
                // time.
                Slog.wtf(TAG,
                        string(userId, packageName) + " has 0 EJ quota without running anything");
                Slog.wtf(TAG, packageToString(userId, packageName)
                        + " has 0 EJ quota without running anything");
                return;
            }
        }
@@ -2272,7 +2226,6 @@ public final class QuotaController extends StateController {
        public void dump(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) {
            final long token = proto.start(fieldId);

            mPkg.dumpDebug(proto, StateControllerProto.QuotaController.Timer.PKG);
            proto.write(StateControllerProto.QuotaController.Timer.IS_ACTIVE, isActive());
            proto.write(StateControllerProto.QuotaController.Timer.START_TIME_ELAPSED,
                    mStartTimeElapsed);
@@ -2381,7 +2334,6 @@ public final class QuotaController extends StateController {
        public void dump(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);

            mPkg.dumpDebug(proto, StateControllerProto.QuotaController.TopAppTimer.PKG);
            proto.write(StateControllerProto.QuotaController.TopAppTimer.IS_ACTIVE, isActive());
            proto.write(StateControllerProto.QuotaController.TopAppTimer.START_TIME_ELAPSED,
                    mStartTimeElapsed);
@@ -2413,7 +2365,7 @@ public final class QuotaController extends StateController {
    void updateStandbyBucket(
            final int userId, final @NonNull String packageName, final int bucketIndex) {
        if (DEBUG) {
            Slog.i(TAG, "Moving pkg " + string(userId, packageName)
            Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName)
                    + " to bucketIndex " + bucketIndex);
        }
        List<JobStatus> restrictedChanges = new ArrayList<>();
@@ -2641,7 +2593,7 @@ public final class QuotaController extends StateController {
                        String packageName = (String) msg.obj;
                        int userId = msg.arg1;
                        if (DEBUG) {
                            Slog.d(TAG, "Checking pkg " + string(userId, packageName));
                            Slog.d(TAG, "Checking pkg " + packageToString(userId, packageName));
                        }
                        if (maybeUpdateConstraintForPkgLocked(sElapsedRealtimeClock.millis(),
                                userId, packageName)) {
@@ -2722,7 +2674,7 @@ public final class QuotaController extends StateController {
                        final String pkgName = event.getPackageName();
                        if (DEBUG) {
                            Slog.d(TAG, "Processing event " + event.getEventType()
                                    + " for " + string(userId, pkgName));
                                    + " for " + packageToString(userId, pkgName));
                        }
                        switch (event.getEventType()) {
                            case UsageEvents.Event.ACTIVITY_RESUMED:
@@ -4119,7 +4071,7 @@ public final class QuotaController extends StateController {
                final String pkgName = mExecutionStatsCache.keyAt(u, p);
                ExecutionStats[] stats = mExecutionStatsCache.valueAt(u, p);

                pw.println(string(userId, pkgName));
                pw.println(packageToString(userId, pkgName));
                pw.increaseIndent();
                for (int i = 0; i < stats.length; ++i) {
                    ExecutionStats executionStats = stats[i];
@@ -4143,7 +4095,7 @@ public final class QuotaController extends StateController {
                final String pkgName = mEJStats.keyAt(u, p);
                ShrinkableDebits debits = mEJStats.valueAt(u, p);

                pw.print(string(userId, pkgName));
                pw.print(packageToString(userId, pkgName));
                pw.print(": ");
                debits.dumpLocked(pw);
            }