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

Commit 463c77b0 authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Allow CMP exemption for restricted bucket due to reason timeout.

In case of media backup jobs, we want to run them even when the
app is in a restricted standby bucket if the reason is timed out
instead of some other reason. This is to ensure that backup jobs
are allowed to run even if the user doesn't manually use the app.

Bug: 409606405
Test: atest ./services/tests/mockingservicestests/src/com/android/server/job/controllers/*Test.java
Test: atest ./services/tests/mockingservicestests/src/com/android/server/job/*Test.java
Flag: com.android.server.job.allow_cmp_exemption_for_restricted_bucket
Change-Id: I12c8c7d941d57b2d0c57bf39596236506edbdb94
parent d4fca214
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -119,3 +119,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "allow_cmp_exemption_for_restricted_bucket"
    namespace: "backstage_power"
    description: "Allow CMP exemption for apps in Restricted bucket due to timeout reason"
    bug: "409606405"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -4725,6 +4725,20 @@ public class JobSchedulerService extends com.android.server.SystemService
        return bucket;
    }

    public static int standbyBucketReasonForPackage(String packageName, int userId,
            long elapsedNow) {
        int reason = sUsageStatsManagerInternal != null
                ? sUsageStatsManagerInternal.getAppStandbyBucketReason(packageName, userId,
                        elapsedNow)
                : UsageStatsManager.REASON_MAIN_DEFAULT;

        if (DEBUG_STANDBY) {
            Slog.v(TAG, packageName + "/" + userId + " standby bucket reason: "
                    + UsageStatsManager.reasonToString(reason));
        }
        return reason;
    }

    static int safelyScaleBytesToKBForHistogram(long bytes) {
        long kilobytes = bytes / 1000;
        // Anything over Integer.MAX_VALUE or under Integer.MIN_VALUE isn't expected and will
+3 −1
Original line number Diff line number Diff line
@@ -1517,9 +1517,11 @@ public final class JobStore {
            // And now we're done
            final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
                    sourceUserId, nowElapsed);
            final int appBucketReason = JobSchedulerService.standbyBucketReasonForPackage(
                    sourcePackageName, sourceUserId, nowElapsed);
            JobStatus js = new JobStatus(
                    builtJob, uid, intern(sourcePackageName), sourceUserId,
                    appBucket, namespace, sourceTag,
                    appBucket, appBucketReason, namespace, sourceTag,
                    elapsedRuntimes.first, elapsedRuntimes.second,
                    lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTime,
                    (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
+63 −20
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.job.controllers;

import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;

import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -34,6 +36,7 @@ import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.app.job.PendingJobReasonsInfo;
import android.app.job.UserVisibleJobSummary;
import android.app.usage.UsageStatsManager;
import android.content.ClipData;
import android.content.ComponentName;
import android.net.Network;
@@ -61,6 +64,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.job.Flags;
import com.android.server.job.GrantedUriPermissions;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
@@ -334,6 +338,11 @@ public final class JobStatus {
     */
    private int standbyBucket;

    /**
     * The reason why the app is in the current standby bucket.
     */
    private int mStandbyBucketReason;

    /**
     * Whether we've logged an error due to standby bucket mismatch with active uid state.
     */
@@ -608,6 +617,8 @@ public final class JobStatus {
     * @param standbyBucket The standby bucket that the source package is currently assigned to,
     *     cached here for speed of handling during runnability evaluations (and updated when bucket
     *     assignments are changed)
     * @param standbyBucketReason The reason why the app is in the current standby bucket denoted
     *     by {@code standbyBucket}.
     * @param namespace The custom namespace the app put this job in.
     * @param tag A string associated with the job for debugging/logging purposes.
     * @param numFailures Count of how many times this job has requested a reschedule because
@@ -625,7 +636,8 @@ public final class JobStatus {
     * @param internalFlags Non-API property flags about this job
     */
    private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
            int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
            int sourceUserId, int standbyBucket, int standbyBucketReason,
            @Nullable String namespace, String tag,
            int numFailures, int mNumAbandonedFailures, int numSystemStops,
            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
            long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
@@ -633,6 +645,7 @@ public final class JobStatus {
            int dynamicConstraints) {
        this.callingUid = callingUid;
        this.standbyBucket = standbyBucket;
        this.mStandbyBucketReason = standbyBucketReason;
        mNamespace = namespace;
        mNamespaceHash = generateNamespaceHash(namespace);
        mLoggingJobId = generateLoggingId(namespace, job.getId());
@@ -771,8 +784,8 @@ public final class JobStatus {
    public JobStatus(JobStatus jobStatus) {
        this(jobStatus.getJob(), jobStatus.getUid(),
                jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
                jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
                jobStatus.getSourceTag(), jobStatus.getNumFailures(),
                jobStatus.getStandbyBucket(), jobStatus.getStandbyBucketReason(),
                jobStatus.getNamespace(), jobStatus.getSourceTag(), jobStatus.getNumFailures(),
                jobStatus.getNumAbandonedFailures(), jobStatus.getNumSystemStops(),
                jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
@@ -801,14 +814,14 @@ public final class JobStatus {
     * standby bucket is whatever the OS thinks it should be at this moment.
     */
    public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
            int standbyBucket, @Nullable String namespace, String sourceTag,
            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
            int standbyBucket, int standbyBucketReason, @Nullable String namespace,
            String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
            long lastSuccessfulRunTime, long lastFailedRunTime,
            long cumulativeExecutionTimeMs,
            Pair<Long, Long> persistedExecutionTimesUTC,
            int innerFlags, int dynamicConstraints) {
        this(job, callingUid, sourcePkgName, sourceUserId,
                standbyBucket, namespace,
                standbyBucket, standbyBucketReason, namespace,
                sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
                /* mNumAbandonedFailures */ 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
@@ -836,8 +849,8 @@ public final class JobStatus {
            long cumulativeExecutionTimeMs) {
        this(rescheduling.job, rescheduling.getUid(),
                rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
                rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
                rescheduling.getSourceTag(), numFailures,
                rescheduling.getStandbyBucket(), rescheduling.getStandbyBucketReason(),
                rescheduling.getNamespace(), rescheduling.getSourceTag(), numFailures,
                mNumAbandonedFailures, numSystemStops,
                newEarliestRuntimeElapsedMillis,
                newLatestRuntimeElapsedMillis,
@@ -876,8 +889,15 @@ public final class JobStatus {

        int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                sourceUserId, elapsedNow);
        // TODO: b/409606405 - It's possible for where the standby bucket changes between the
        // time the bucket is queried and the reasoning for the bucket change is queried.
        // Although the standby bucket change callback will correct this eventually, it'd be
        // ideal to query the bucket and the reasoning in a single call to prevent potential
        // temporary inconsistent state.
        int standbyBucketReason = JobSchedulerService.standbyBucketReasonForPackage(
                jobPackage, sourceUserId, elapsedNow);
        return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
                standbyBucket, namespace, tag, /* numFailures */ 0,
                standbyBucket, standbyBucketReason, namespace, tag, /* numFailures */ 0,
                /* mNumAbandonedFailures */ 0, /* numSystemStops */ 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
@@ -1299,16 +1319,7 @@ public final class JobStatus {
            return ACTIVE_INDEX;
        }

        final int bucketWithBackupExemption;
        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
                && mHasMediaBackupExemption) {
            // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
            // media backup jobs are important to the user, and the source package may not have
            // been used directly in a while.
            bucketWithBackupExemption = Math.min(WORKING_INDEX, actualBucket);
        } else {
            bucketWithBackupExemption = actualBucket;
        }
        final int bucketWithBackupExemption = getBucketWithBackupExemption(actualBucket);

        // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
        // (potentially because it's used frequently by the user), limit its effective bucket
@@ -1326,12 +1337,43 @@ public final class JobStatus {
        return bucketWithBackupExemption;
    }

    private int getBucketWithBackupExemption(int actualBucket) {
        final int standbyBucketMainReason = UsageStatsManager.getMainReason(
                getStandbyBucketReason());
        final int bucketWithBackupExemption;
        final boolean isBucketEligibleForExemption;
        if (actualBucket == NEVER_INDEX) {
            isBucketEligibleForExemption = false;
        } else if (actualBucket == RESTRICTED_INDEX
                && (!Flags.allowCmpExemptionForRestrictedBucket()
                        || standbyBucketMainReason != REASON_MAIN_TIMEOUT)) {
            isBucketEligibleForExemption = false;
        } else {
            isBucketEligibleForExemption = true;
        }
        if (isBucketEligibleForExemption && mHasMediaBackupExemption) {
            // Treat it as if it's at most WORKING_INDEX (lower index grants higher quota) since
            // media backup jobs are important to the user, and the source package may not have
            // been used directly in a while.
            bucketWithBackupExemption = Math.min(WORKING_INDEX, actualBucket);
        } else {
            bucketWithBackupExemption = actualBucket;
        }
        return bucketWithBackupExemption;
    }

    /** Returns the real standby bucket of the job. */
    public int getStandbyBucket() {
        return standbyBucket;
    }

    public void setStandbyBucket(int newBucket) {
    /** Returns the reason for the standby bucket of the job. */
    @VisibleForTesting
    int getStandbyBucketReason() {
        return mStandbyBucketReason;
    }

    public void setStandbyBucket(int newBucket, int reason) {
        if (newBucket == RESTRICTED_INDEX) {
            // Adding to the bucket.
            addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS);
@@ -1341,6 +1383,7 @@ public final class JobStatus {
        }

        standbyBucket = newBucket;
        mStandbyBucketReason = reason;
        mLoggedBucketMismatch = false;
    }

+4 −3
Original line number Diff line number Diff line
@@ -2534,14 +2534,15 @@ public final class QuotaController extends StateController {
            // Update job bookkeeping out of band.
            AppSchedulingModuleThread.getHandler().post(() -> {
                final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
                updateStandbyBucket(userId, packageName, bucketIndex);
                updateStandbyBucket(userId, packageName, bucketIndex, reason);
            });
        }
    }

    @VisibleForTesting
    void updateStandbyBucket(
            final int userId, final @NonNull String packageName, final int bucketIndex) {
            final int userId, final @NonNull String packageName, final int bucketIndex,
            final int reason) {
        if (DEBUG) {
            Slog.i(TAG, "Moving pkg " + packageToString(userId, packageName)
                    + " to bucketIndex " + bucketIndex);
@@ -2566,7 +2567,7 @@ public final class QuotaController extends StateController {
                        && bucketIndex != js.getStandbyBucket()) {
                    restrictedChanges.add(js);
                }
                js.setStandbyBucket(bucketIndex);
                js.setStandbyBucket(bucketIndex, reason);
            }
            Timer timer = mPkgTimers.get(userId, packageName);
            if (timer != null && timer.isActive()) {
Loading