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

Commit 0cf9ed29 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Standby exemption for system gallery's backup jobs"

parents 08b44c42 08126470
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -896,7 +896,7 @@ public class JobInfo implements Parcelable {
         * @param flags Flags for the observer.
         */
        public TriggerContentUri(@NonNull Uri uri, @Flags int flags) {
            mUri = uri;
            mUri = Objects.requireNonNull(uri);
            mFlags = flags;
        }

+5 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.job;

import android.annotation.NonNull;
import android.app.job.JobInfo;
import android.util.proto.ProtoOutputStream;

@@ -44,6 +45,10 @@ public interface JobSchedulerInternal {
    void removeBackingUpUid(int uid);
    void clearAllBackingUpUids();

    /** Returns the package responsible for backing up media on the device. */
    @NonNull
    String getMediaBackupPackage();

    /**
     * The user has started interacting with the app.  Take any appropriate action.
     */
+12 −0
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.ArrayUtils;
@@ -248,6 +249,9 @@ public class JobSchedulerService extends com.android.server.SystemService
     */
    private final List<JobRestriction> mJobRestrictions;

    @NonNull
    private final String mSystemGalleryPackage;

    private final CountQuotaTracker mQuotaTracker;
    private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";

@@ -1394,6 +1398,9 @@ public class JobSchedulerService extends com.android.server.SystemService
        mJobRestrictions = new ArrayList<>();
        mJobRestrictions.add(new ThermalStatusRestriction(this));

        mSystemGalleryPackage = Objects.requireNonNull(
                context.getString(R.string.config_systemGallery));

        // If the job store determined that it can't yet reschedule persisted jobs,
        // we need to start watching the clock.
        if (!mJobs.jobTimesInflatedValid()) {
@@ -2358,6 +2365,11 @@ public class JobSchedulerService extends com.android.server.SystemService
            }
        }

        @Override
        public String getMediaBackupPackage() {
            return mSystemGalleryPackage;
        }

        @Override
        public void reportAppUsage(String packageName, int userId) {
            JobSchedulerService.this.reportAppUsage(packageName, userId);
+35 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_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 android.app.AppGlobals;
@@ -30,6 +31,7 @@ import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Pair;
@@ -207,6 +209,18 @@ public final class JobStatus {
     */
    private int mDynamicConstraints = 0;

    /**
     * Indicates whether the job is responsible for backing up media, so we can be lenient in
     * applying standby throttling.
     *
     * Doesn't exempt jobs with a deadline constraint, as they can be started without any content or
     * network changes, in which case this exemption does not make sense.
     *
     * TODO(b/149519887): Use a more explicit signal, maybe an API flag, that the scheduling package
     * needs to provide at the time of scheduling a job.
     */
    private final boolean mHasMediaBackupExemption;

    // Set to true if doze constraint was satisfied due to app being whitelisted.
    public boolean dozeWhitelisted;

@@ -415,9 +429,11 @@ public final class JobStatus {
        this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
        this.numFailures = numFailures;

        boolean requiresNetwork = false;
        int requiredConstraints = job.getConstraintFlags();
        if (job.getRequiredNetwork() != null) {
            requiredConstraints |= CONSTRAINT_CONNECTIVITY;
            requiresNetwork = true;
        }
        if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
            requiredConstraints |= CONSTRAINT_TIMING_DELAY;
@@ -425,8 +441,16 @@ public final class JobStatus {
        if (latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
            requiredConstraints |= CONSTRAINT_DEADLINE;
        }
        boolean mediaOnly = false;
        if (job.getTriggerContentUris() != null) {
            requiredConstraints |= CONSTRAINT_CONTENT_TRIGGER;
            mediaOnly = true;
            for (JobInfo.TriggerContentUri uri : job.getTriggerContentUris()) {
                if (!MediaStore.AUTHORITY.equals(uri.getUri().getAuthority())) {
                    mediaOnly = false;
                    break;
                }
            }
        }
        this.requiredConstraints = requiredConstraints;
        mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST;
@@ -450,6 +474,9 @@ public final class JobStatus {
            // our source UID into place.
            job.getRequiredNetwork().networkCapabilities.setSingleUid(this.sourceUid);
        }
        final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
        mHasMediaBackupExemption = !job.hasLateConstraint() && mediaOnly && requiresNetwork
                && this.sourcePackageName.equals(jsi.getMediaBackupPackage());
    }

    /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -545,7 +572,6 @@ public final class JobStatus {

        int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                sourceUserId, elapsedNow);
        JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
        return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
                standbyBucket, tag, 0,
                earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
@@ -734,7 +760,14 @@ public final class JobStatus {
            // like other ACTIVE apps.
            return ACTIVE_INDEX;
        }
        return getStandbyBucket();
        final int actualBucket = getStandbyBucket();
        if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
                && mHasMediaBackupExemption) {
            // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
            // source package may not have been used directly in a while.
            return Math.min(WORKING_INDEX, actualBucket);
        }
        return actualBucket;
    }

    /** Returns the real standby bucket of the job. */
+92 −1
Original line number Diff line number Diff line
@@ -19,6 +19,12 @@ package com.android.server.job.controllers;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
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.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
@@ -34,13 +40,16 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.SparseIntArray;

import androidx.test.runner.AndroidJUnit4;

@@ -52,6 +61,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;

@@ -61,7 +71,12 @@ import java.time.ZoneOffset;
@RunWith(AndroidJUnit4.class)
public class JobStatusTest {
    private static final double DELTA = 0.00001;
    private static final String TEST_PACKAGE = "job.test.package";
    private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(TEST_PACKAGE, "test");
    private static final Uri TEST_MEDIA_URI = Uri.parse("content://media/path/to/media");

    @Mock
    private JobSchedulerInternal mJobSchedulerInternal;
    private MockitoSession mMockingSession;

    @Before
@@ -71,7 +86,7 @@ public class JobStatusTest {
                .strictness(Strictness.LENIENT)
                .mockStatic(LocalServices.class)
                .startMocking();
        doReturn(mock(JobSchedulerInternal.class))
        doReturn(mJobSchedulerInternal)
                .when(() -> LocalServices.getService(JobSchedulerInternal.class));
        doReturn(mock(PackageManagerInternal.class))
                .when(() -> LocalServices.getService(PackageManagerInternal.class));
@@ -94,6 +109,82 @@ public class JobStatusTest {
        }
    }

    private static void assertEffectiveBucketForMediaExemption(JobStatus jobStatus,
            boolean exemptionGranted) {
        final SparseIntArray effectiveBucket = new SparseIntArray();
        effectiveBucket.put(ACTIVE_INDEX, ACTIVE_INDEX);
        effectiveBucket.put(WORKING_INDEX, WORKING_INDEX);
        effectiveBucket.put(FREQUENT_INDEX, exemptionGranted ? WORKING_INDEX : FREQUENT_INDEX);
        effectiveBucket.put(RARE_INDEX, exemptionGranted ? WORKING_INDEX : RARE_INDEX);
        effectiveBucket.put(NEVER_INDEX, NEVER_INDEX);
        effectiveBucket.put(RESTRICTED_INDEX, RESTRICTED_INDEX);
        for (int i = 0; i < effectiveBucket.size(); i++) {
            jobStatus.setStandbyBucket(effectiveBucket.keyAt(i));
            assertEquals(effectiveBucket.valueAt(i), jobStatus.getEffectiveStandbyBucket());
        }
    }

    @Test
    public void testMediaBackupExemption_lateConstraint() {
        final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
                .setOverrideDeadline(12)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
        assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
    }

    @Test
    public void testMediaBackupExemption_noConnectivityConstraint() {
        final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
        assertEffectiveBucketForMediaExemption(createJobStatus(triggerContentJob), false);
    }

    @Test
    public void testMediaBackupExemption_noContentTriggerConstraint() {
        final JobInfo networkJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
        assertEffectiveBucketForMediaExemption(createJobStatus(networkJob), false);
    }

    @Test
    public void testMediaBackupExemption_wrongSourcePackage() {
        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn("not.test.package");
        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
    }

    @Test
    public void testMediaBackupExemption_nonMediaUri() {
        final Uri nonMediaUri = Uri.parse("content://not-media/any/path");
        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
                .addTriggerContentUri(new JobInfo.TriggerContentUri(nonMediaUri, 0))
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), false);
    }

    @Test
    public void testMediaBackupExemptionGranted() {
        final JobInfo networkContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                .addTriggerContentUri(new JobInfo.TriggerContentUri(TEST_MEDIA_URI, 0))
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                .build();
        when(mJobSchedulerInternal.getMediaBackupPackage()).thenReturn(TEST_PACKAGE);
        assertEffectiveBucketForMediaExemption(createJobStatus(networkContentJob), true);
    }

    @Test
    public void testFraction() throws Exception {
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
Loading