From 59c3b84f00a3450c91080a5039b226fa2a495905 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 24 Jan 2023 21:24:13 +0000 Subject: [PATCH] Remove jobs from the pending queue on user stop. Remove jobs from the pending queue when the user stops the app via Task Manager so we don't immediately restart the process. Bug: 261999509 Test: atest FrameworksMockingServicesTests:JobSchedulerServiceTest Change-Id: I76f9bb9778ca9b2fd3cfaef99430334547206ad4 --- .../server/job/JobSchedulerService.java | 24 ++++++- .../server/job/JobSchedulerServiceTest.java | 69 +++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) 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 299cb6cddbdf1..e8fcdd2979ded 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1584,6 +1584,12 @@ public class JobSchedulerService extends com.android.server.SystemService return reason; } + @VisibleForTesting + @JobScheduler.PendingJobReason + int getPendingJobReason(JobStatus job) { + return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId()); + } + @JobScheduler.PendingJobReason @GuardedBy("mLock") private int getPendingJobReasonLocked(int uid, String namespace, int jobId) { @@ -1694,7 +1700,8 @@ public class JobSchedulerService extends com.android.server.SystemService } } - private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) { + @VisibleForTesting + void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) { final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId); if (packageUid < 0) { Slog.wtf(TAG, "Asked to stop jobs of an unknown package"); @@ -1716,6 +1723,21 @@ public class JobSchedulerService extends com.android.server.SystemService // to stop only that work, B's jobs would be demoted as well. // TODO(255768978): make it possible to demote only the relevant subset of jobs job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER); + + // The app process will be killed soon. There's no point keeping its jobs in + // the pending queue to try and start them. + if (mPendingJobQueue.remove(job)) { + synchronized (mPendingJobReasonCache) { + SparseIntArray jobIdToReason = mPendingJobReasonCache.get( + job.getUid(), job.getNamespace()); + if (jobIdToReason == null) { + jobIdToReason = new SparseIntArray(); + mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), + jobIdToReason); + } + jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER); + } + } } } } 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 7281fafc93f2e..ed78e720df73b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -30,7 +30,9 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -95,6 +97,8 @@ public class JobSchedulerServiceTest { private ActivityManagerInternal mActivityMangerInternal; @Mock private Context mContext; + @Mock + private PackageManagerInternal mPackageManagerInternal; private class TestJobSchedulerService extends JobSchedulerService { TestJobSchedulerService(Context context) { @@ -121,6 +125,8 @@ public class JobSchedulerServiceTest { .when(() -> LocalServices.getService(AppStandbyInternal.class)); doReturn(mock(BatteryManagerInternal.class)) .when(() -> LocalServices.getService(BatteryManagerInternal.class)); + doReturn(mPackageManagerInternal) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); doReturn(mock(UsageStatsManagerInternal.class)) .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); when(mContext.getString(anyInt())).thenReturn("some_test_string"); @@ -138,9 +144,6 @@ public class JobSchedulerServiceTest { // Used in JobConcurrencyManager. doReturn(mock(UserManagerInternal.class)) .when(() -> LocalServices.getService(UserManagerInternal.class)); - // Used in JobStatus. - doReturn(mock(PackageManagerInternal.class)) - .when(() -> LocalServices.getService(PackageManagerInternal.class)); // Called via IdleController constructor. when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); when(mContext.getResources()).thenReturn(mock(Resources.class)); @@ -197,8 +200,13 @@ public class JobSchedulerServiceTest { private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, int callingUid) { + return createJobStatus(testTag, jobInfoBuilder, callingUid, "com.android.test"); + } + + private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, + int callingUid, String sourcePkg) { return JobStatus.createFromJobInfo( - jobInfoBuilder.build(), callingUid, "com.android.test", 0, "JSSTest", testTag); + jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag); } private void grantRunLongJobsPermission(boolean grant) { @@ -1238,4 +1246,57 @@ public class JobSchedulerServiceTest { 0, "JSSTest", "")); } } + + /** Tests that jobs are removed from the pending list if the user stops the app. */ + @Test + public void testUserStopRemovesPending() { + spyOn(mService); + + JobStatus job1a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 1, "pkg1"); + JobStatus job1b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 1, "pkg1"); + JobStatus job2a = createJobStatus("testUserStopRemovesPending", + createJobInfo(1), 2, "pkg2"); + JobStatus job2b = createJobStatus("testUserStopRemovesPending", + createJobInfo(2), 2, "pkg2"); + doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0); + doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1); + doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0); + + mService.getPendingJobQueue().clear(); + mService.getPendingJobQueue().add(job1a); + mService.getPendingJobQueue().add(job1b); + mService.getPendingJobQueue().add(job2a); + mService.getPendingJobQueue().add(job2b); + mService.getJobStore().add(job1a); + mService.getJobStore().add(job1b); + mService.getJobStore().add(job2a); + mService.getJobStore().add(job2b); + + mService.stopUserVisibleJobsInternal("pkg1", 1); + assertEquals(4, mService.getPendingJobQueue().size()); + assertTrue(mService.getPendingJobQueue().contains(job1a)); + assertTrue(mService.getPendingJobQueue().contains(job1b)); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.stopUserVisibleJobsInternal("pkg1", 0); + assertEquals(2, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a)); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1b)); + assertTrue(mService.getPendingJobQueue().contains(job2a)); + assertTrue(mService.getPendingJobQueue().contains(job2b)); + + mService.stopUserVisibleJobsInternal("pkg2", 0); + assertEquals(0, mService.getPendingJobQueue().size()); + assertFalse(mService.getPendingJobQueue().contains(job1a)); + assertFalse(mService.getPendingJobQueue().contains(job1b)); + assertFalse(mService.getPendingJobQueue().contains(job2a)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2a)); + assertFalse(mService.getPendingJobQueue().contains(job2b)); + assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b)); + } }