summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sanath Kumar <sanathku@google.com> 2024-12-09 15:25:08 -0600
committer Sanath Kumar <sanathku@google.com> 2024-12-11 14:17:54 -0600
commite38ab1a9611dcf6bf519b0077170d896e4784504 (patch)
tree8c9d2965d7279d62a76f8e0cef1f2ee57078cdfb
parentfea9a83deb57670810a83adf1cb938262eceabb2 (diff)
JobScheduler: Enable abandoned job overrides
Application compatibility overrides now allow disabling abandoned job detection and handling. This override only takes effect if the abandoned job feature is enabled. Test: atest CtsJobSchedulerTestCases Test: atest FrameworksMockingServicesTests Bug: 372529068 Flag: android.app.job.handle_abandoned_jobs Change-Id: Ib19ef30a74b974edd96ecfa2090764e95bcd1e30
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobParameters.java22
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java21
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java44
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java59
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java70
7 files changed, 208 insertions, 31 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index b6f0c04b8c16..7fef4e502c97 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -23,6 +23,10 @@ import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManager;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.pm.PackageManager;
@@ -349,6 +353,16 @@ public class JobParameters implements Parcelable {
private JobCleanupCallback mJobCleanupCallback;
@Nullable
private Cleaner.Cleanable mCleanable;
+ /**
+ * Override handling of abandoned jobs in the system. Overriding this change
+ * will prevent the system to handle abandoned jobs and report it as a new
+ * stop reason STOP_REASON_TIMEOUT_ABANDONED.
+ * @hide
+ */
+ @ChangeId
+ @Disabled
+ @Overridable
+ public static final long OVERRIDE_HANDLE_ABANDONED_JOBS = 372529068L;
/** @hide */
public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras,
@@ -677,6 +691,10 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void enableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
// JobParameters objects are passed by reference in local Binder
// transactions for clients running as SYSTEM. The life cycle of the
// JobParameters objects are no longer controlled by the client.
@@ -695,6 +713,10 @@ public class JobParameters implements Parcelable {
* @hide
*/
public void disableCleaner() {
+ if (!Flags.handleAbandonedJobs()
+ || Compatibility.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS)) {
+ return;
+ }
if (mJobCleanupCallback != null) {
mJobCleanupCallback.disableCleaner();
if (mCleanable != null) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 69b83cc02b54..d460dcc65473 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -165,11 +165,9 @@ public abstract class JobServiceEngine {
case MSG_EXECUTE_JOB: {
final JobParameters params = (JobParameters) msg.obj;
try {
- if (Flags.handleAbandonedJobs()) {
- params.enableCleaner();
- }
+ params.enableCleaner();
boolean workOngoing = JobServiceEngine.this.onStartJob(params);
- if (Flags.handleAbandonedJobs() && !workOngoing) {
+ if (!workOngoing) {
params.disableCleaner();
}
ackStartMessage(params, workOngoing);
@@ -196,9 +194,7 @@ public abstract class JobServiceEngine {
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
- if (Flags.handleAbandonedJobs()) {
- params.disableCleaner();
- }
+ params.disableCleaner();
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 8fad79a845b4..7922c6003beb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
@@ -1985,8 +1986,8 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
jobStatus.getJob().getBackoffPolicy() + 1,
- shouldUseAggressiveBackoff(jobStatus.getNumAbandonedFailures()));
-
+ shouldUseAggressiveBackoff(
+ jobStatus.getNumAbandonedFailures(), jobStatus.getSourceUid()));
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2431,7 +2432,8 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
cancelled.getJob().getBackoffPolicy() + 1,
- shouldUseAggressiveBackoff(cancelled.getNumAbandonedFailures()));
+ shouldUseAggressiveBackoff(
+ cancelled.getNumAbandonedFailures(), cancelled.getSourceUid()));
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -3023,6 +3025,7 @@ public class JobSchedulerService extends com.android.server.SystemService
int numFailures = failureToReschedule.getNumFailures();
int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
int numSystemStops = failureToReschedule.getNumSystemStops();
+ final int uid = failureToReschedule.getSourceUid();
// We should back off slowly if JobScheduler keeps stopping the job,
// but back off immediately if the issue appeared to be the app's fault
// or the user stopped the job somehow.
@@ -3032,6 +3035,7 @@ public class JobSchedulerService extends com.android.server.SystemService
|| stopReason == JobParameters.STOP_REASON_USER) {
numFailures++;
} else if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
&& internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
numAbandonedFailures++;
numFailures++;
@@ -3040,7 +3044,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
int backoffPolicy = job.getBackoffPolicy();
- if (shouldUseAggressiveBackoff(numAbandonedFailures)) {
+ if (shouldUseAggressiveBackoff(numAbandonedFailures, uid)) {
backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
}
@@ -3111,8 +3115,9 @@ public class JobSchedulerService extends com.android.server.SystemService
* @return {@code true} if the given number of abandoned failures indicates that JobScheduler
* should use an aggressive backoff policy.
*/
- public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) {
+ public boolean shouldUseAggressiveBackoff(int numAbandonedFailures, int uid) {
return android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(OVERRIDE_HANDLE_ABANDONED_JOBS, uid)
&& numAbandonedFailures
> mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
}
@@ -3222,7 +3227,9 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
- if (android.app.job.Flags.handleAbandonedJobs()) {
+ if (android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())) {
jobTimedOut |= (debugStopReason
== JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
}
@@ -3308,6 +3315,8 @@ public class JobSchedulerService extends com.android.server.SystemService
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, jobStatus.getSourceUid())
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
if (rescheduledJob != null
&& !rescheduledJob.shouldTreatAsUserInitiatedJob()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 2b401c8ff6b1..ebfda527001d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -16,6 +16,8 @@
package com.android.server.job;
+import static android.app.job.JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS;
+
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.safelyScaleBytesToKBForHistogram;
@@ -550,7 +552,8 @@ public final class JobServiceContext implements ServiceConnection {
job.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
job.getJob().getBackoffPolicy() + 1,
- mService.shouldUseAggressiveBackoff(job.getNumAbandonedFailures()));
+ mService.shouldUseAggressiveBackoff(
+ job.getNumAbandonedFailures(), job.getSourceUid()));
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1461,7 +1464,10 @@ public final class JobServiceContext implements ServiceConnection {
final StringBuilder debugStopReason = new StringBuilder("client timed out");
if (android.app.job.Flags.handleAbandonedJobs()
- && executing != null && executing.isAbandoned()) {
+ && executing != null
+ && !CompatChanges.isChangeEnabled(
+ OVERRIDE_HANDLE_ABANDONED_JOBS, executing.getSourceUid())
+ && executing.isAbandoned()) {
final String abandonedMessage = " and maybe abandoned";
stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
@@ -1689,7 +1695,8 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getNumAbandonedFailures(),
/* 0 is reserved for UNKNOWN_POLICY */
completedJob.getJob().getBackoffPolicy() + 1,
- mService.shouldUseAggressiveBackoff(completedJob.getNumAbandonedFailures()));
+ mService.shouldUseAggressiveBackoff(
+ completedJob.getNumAbandonedFailures(), completedJob.getSourceUid()));
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER,
JobSchedulerService.TRACE_TRACK_NAME, getId());
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
index 3b6c86e3c94f..0c92c10e2523 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobParametersTest.java
@@ -29,15 +29,20 @@ import android.app.job.IJobCallback;
import android.app.job.JobParameters;
import android.net.Uri;
import android.os.Parcel;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
+import libcore.junit.util.compat.CoreCompatChangeRule;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -47,7 +52,10 @@ public class JobParametersTest {
private static final int TEST_JOB_ID_1 = 123;
private static final String TEST_NAMESPACE = "TEST_NAMESPACE";
private static final String TEST_DEBUG_STOP_REASON = "TEST_DEBUG_STOP_REASON";
- @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new CoreCompatChangeRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -129,9 +137,10 @@ public class JobParametersTest {
}
/** Test to verify that the JobParameters Cleaner is disabled */
- @RequiresFlagsEnabled(FLAG_HANDLE_ABANDONED_JOBS)
@Test
- public void testCleanerWithLeakedJobCleanerDisabled_flagHandleAbandonedJobs() {
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testCleanerWithLeakedNoJobCleaner_EnableFlagDisableCompatHandleAbandonedJobs() {
// Inject real JobCallbackCleanup
JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel);
@@ -150,4 +159,31 @@ public class JobParametersTest {
assertThat(jobParameters.getCleanable()).isNull();
assertThat(jobParameters.getJobCleanupCallback()).isNull();
}
+
+ /**
+ * Test to verify that the JobParameters Cleaner is not enabled
+ * when the compat change is enabled and the flag is enabled
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testCleanerWithLeakedNoJobCleaner_EnableFlagEnableCompatHandleAbandonedJobs() {
+ // Inject real JobCallbackCleanup
+ JobParameters jobParameters = JobParameters.CREATOR.createFromParcel(mMockParcel);
+
+ // Enable the cleaner
+ jobParameters.enableCleaner();
+
+ // Verify the cleaner is not enabled
+ assertThat(jobParameters.getCleanable()).isNull();
+ assertThat(jobParameters.getJobCleanupCallback()).isNull();
+
+ // Disable the cleaner
+ jobParameters.disableCleaner();
+
+ // Verify the cleaner is disabled
+ assertThat(jobParameters.getCleanable()).isNull();
+ assertThat(jobParameters.getJobCleanupCallback()).isNull();
+ }
+
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index c831475577d8..ae2cbab8b1de 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -53,6 +53,7 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.UiModeManager;
import android.app.job.JobInfo;
@@ -60,6 +61,7 @@ import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -79,7 +81,6 @@ import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
@@ -104,10 +105,14 @@ import com.android.server.job.restrictions.ThermalStatusRestriction;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
@@ -119,6 +124,7 @@ import java.time.Duration;
import java.time.ZoneOffset;
public class JobSchedulerServiceTest {
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
private static final int TEST_UID = 10123;
@@ -140,8 +146,13 @@ public class JobSchedulerServiceTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
private ChargingPolicyChangeListener mChargingPolicyChangeListener;
+ private int mSourceUid;
+
private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context) {
super(context);
@@ -156,7 +167,6 @@ public class JobSchedulerServiceTest {
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
.mockStatic(PermissionChecker.class)
- .mockStatic(ServiceManager.class)
.startMocking();
// Called in JobSchedulerService constructor.
@@ -225,6 +235,7 @@ public class JobSchedulerServiceTest {
verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
chargingPolicyChangeListenerCaptor.capture());
mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
}
@After
@@ -1062,6 +1073,7 @@ public class JobSchedulerServiceTest {
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
public void testGetRescheduleJobForFailure_abandonedJob() {
final long nowElapsed = sElapsedRealtimeClock.millis();
final long initialBackoffMs = MINUTE_IN_MILLIS;
@@ -1073,6 +1085,9 @@ public class JobSchedulerServiceTest {
assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+ spyOn(originalJob);
+ doReturn(mSourceUid).when(originalJob).getSourceUid();
+
// failure = 1, systemStop = 0, abandoned = 1
JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
JobParameters.STOP_REASON_DEVICE_STATE,
@@ -1080,6 +1095,8 @@ public class JobSchedulerServiceTest {
assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ spyOn(rescheduledJob);
+ doReturn(mSourceUid).when(rescheduledJob).getSourceUid();
// failure = 2, systemstop = 0, abandoned = 2
rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
JobParameters.STOP_REASON_DEVICE_STATE,
@@ -1125,6 +1142,44 @@ public class JobSchedulerServiceTest {
}
/**
+ * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns true
+ * when the number of abandoned jobs is greater than the threshold.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testGetRescheduleJobForFailure_EnableFlagDisableCompatCheckAggressiveBackoff() {
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ mSourceUid));
+ assertTrue(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1,
+ mSourceUid));
+ }
+
+ /**
+ * Confirm that {@link JobSchedulerService#shouldUseAggressiveBackoff(int, int)} returns false
+ * always when the compat change is enabled and the flag is enabled.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testGetRescheduleJobForFailure_EnableFlagEnableCompatCheckAggressiveBackoff() {
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF - 1,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ mSourceUid));
+ assertFalse(mService.shouldUseAggressiveBackoff(
+ mService.mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF + 1,
+ mSourceUid));
+ }
+
+ /**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
* returns a job that is correctly marked as demoted by the user.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
index 8c66fd0e684a..904545bd3cc3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.job.JobParameters;
+import android.compat.testing.PlatformCompatChangeRule;
import android.content.Context;
import android.os.Looper;
import android.os.PowerManager;
@@ -43,11 +44,15 @@ import com.android.internal.app.IBatteryStats;
import com.android.server.job.JobServiceContext.JobCallback;
import com.android.server.job.controllers.JobStatus;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
@@ -58,11 +63,14 @@ import java.time.Duration;
import java.time.ZoneOffset;
public class JobServiceContextTest {
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
private static final String TAG = JobServiceContextTest.class.getSimpleName();
@ClassRule
public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
@Rule
public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Mock
private JobSchedulerService mMockJobSchedulerService;
@Mock
@@ -86,13 +94,13 @@ public class JobServiceContextTest {
private MockitoSession mMockingSession;
private JobServiceContext mJobServiceContext;
private Object mLock;
+ private int mSourceUid;
@Before
public void setUp() throws Exception {
mMockingSession =
mockitoSession()
.initMocks(this)
- .mockStatic(AppGlobals.class)
.strictness(Strictness.LENIENT)
.startMocking();
JobSchedulerService.sElapsedRealtimeClock =
@@ -111,6 +119,7 @@ public class JobServiceContextTest {
mMockLooper);
spyOn(mJobServiceContext);
mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+ mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
}
@After
@@ -130,11 +139,14 @@ public class JobServiceContextTest {
}
/**
- * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+ * Test that with the compat change disabled and the flag enabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is marked as abandoned.
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutAbandonedJob() {
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagDisableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -143,6 +155,7 @@ public class JobServiceContextTest {
mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
doReturn(true).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
@@ -158,11 +171,14 @@ public class JobServiceContextTest {
}
/**
- * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ * Test that with the compat change enabled and the flag enabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is not marked as abandoned.
*/
@Test
@EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ @EnableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_EnableFlagEnableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -171,7 +187,8 @@ public class JobServiceContextTest {
mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
- doReturn(false).when(mMockJobStatus).isAbandoned();
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
+ doReturn(true).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
mJobServiceContext.handleOpTimeoutLocked();
@@ -186,12 +203,14 @@ public class JobServiceContextTest {
}
/**
- * Test that abandoned jobs that are timed out while the flag is disabled
- * are stopped with the correct stop reason
+ * Test that with the compat change disabled and the flag disabled, abandoned
+ * jobs that are timed out are stopped with the correct stop reason and the
+ * job is not marked as abandoned.
*/
@Test
@DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
- public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutAbandonedJob_DisableFlagDisableCompat() {
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
@@ -201,6 +220,39 @@ public class JobServiceContextTest {
mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
doReturn(true).when(mMockJobStatus).isAbandoned();
+ doReturn(mSourceUid).when(mMockJobStatus).getSourceUid();
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+ synchronized (mLock) {
+ mJobServiceContext.handleOpTimeoutLocked();
+ }
+
+ String stopMessage = captor.getValue();
+ assertEquals("timeout while executing", stopMessage);
+ verify(mMockJobParameters)
+ .setStopReason(
+ JobParameters.STOP_REASON_TIMEOUT,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+ "client timed out");
+ }
+
+ /**
+ * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ @DisableCompatChanges({JobParameters.OVERRIDE_HANDLE_ABANDONED_JOBS})
+ public void testJobServiceContext_TimeoutNoAbandonedJob() {
+ mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ synchronized (mLock) {
+ doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+ }
+ advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+ mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+ mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+ doReturn(false).when(mMockJobStatus).isAbandoned();
mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
mJobServiceContext.handleOpTimeoutLocked();