diff options
author | 2024-12-09 15:25:08 -0600 | |
---|---|---|
committer | 2024-12-11 14:17:54 -0600 | |
commit | e38ab1a9611dcf6bf519b0077170d896e4784504 (patch) | |
tree | 8c9d2965d7279d62a76f8e0cef1f2ee57078cdfb | |
parent | fea9a83deb57670810a83adf1cb938262eceabb2 (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
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(); |