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

This commit is contained in:
TreeHugger Robot
2020-02-14 05:43:26 +00:00
committed by Android (Google) Code Review
6 changed files with 146 additions and 5 deletions

View File

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

View File

@@ -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.
*/

View File

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

View File

@@ -344,7 +344,7 @@ public final class ContentObserverController extends StateController {
mContext.getContentResolver().unregisterContentObserver(obs);
ArrayMap<JobInfo.TriggerContentUri, ObserverInstance> observerOfUser =
mObservers.get(obs.mUserId);
if (observerOfUser != null) {
if (observerOfUser != null) {
observerOfUser.remove(obs.mUri);
}
}

View File

@@ -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. */

View File

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