summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java119
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java6
-rw-r--r--core/java/android/content/Intent.java2
-rw-r--r--services/core/java/com/android/server/content/SyncJobService.java16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java291
6 files changed, 418 insertions, 24 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 7e142a5ddd7b..83db4cbb7e43 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1433,10 +1433,10 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
synchronized (mLock) {
- // Exclude jobs scheduled on behalf of this app for now because SyncManager
+ // Exclude jobs scheduled on behalf of this app because SyncManager
// and other job proxy agents may not know to reschedule the job properly
// after force stop.
- // TODO(209852664): determine how to best handle syncs & other proxied jobs
+ // Proxied jobs will not be allowed to run if the source app is stopped.
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
/* includeSchedulingApp */ true, /* includeSourceApp */ false,
JobParameters.STOP_REASON_USER,
@@ -1448,7 +1448,9 @@ public class JobSchedulerService extends com.android.server.SystemService
}
};
- private String getPackageName(Intent intent) {
+ /** Returns the package name stored in the intent's data. */
+ @Nullable
+ public static String getPackageName(Intent intent) {
Uri uri = intent.getData();
String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
return pkg;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index cd3ba6b9e13e..4aadc903ba23 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -17,18 +17,26 @@
package com.android.server.job.controllers;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
+import static com.android.server.job.JobSchedulerService.getPackageName;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManagerInternal;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.AppStateTracker;
import com.android.server.AppStateTrackerImpl;
import com.android.server.AppStateTrackerImpl.Listener;
@@ -50,6 +58,8 @@ import java.util.function.Predicate;
*
* - the uid-active boolean state expressed by the AppStateTracker. Jobs in 'active'
* uids are inherently eligible to run jobs regardless of the uid's standby bucket.
+ *
+ * - the app's stopped state
*/
public final class BackgroundJobsController extends StateController {
private static final String TAG = "JobScheduler.Background";
@@ -63,9 +73,48 @@ public final class BackgroundJobsController extends StateController {
private final ActivityManagerInternal mActivityManagerInternal;
private final AppStateTrackerImpl mAppStateTracker;
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Boolean> mPackageStoppedState = new SparseArrayMap<>();
private final UpdateJobFunctor mUpdateJobFunctor = new UpdateJobFunctor();
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String pkgName = getPackageName(intent);
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final String action = intent.getAction();
+ if (pkgUid == -1) {
+ Slog.e(TAG, "Didn't get package UID in intent (" + action + ")");
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Got " + action + " for " + pkgUid + "/" + pkgName);
+ }
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_RESTARTED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE);
+ updateJobRestrictionsForUidLocked(pkgUid, false);
+ }
+ }
+ break;
+
+ case Intent.ACTION_PACKAGE_UNSTOPPED: {
+ synchronized (mLock) {
+ mPackageStoppedState.add(pkgUid, pkgName, Boolean.FALSE);
+ updateJobRestrictionsLocked(pkgUid, UNKNOWN);
+ }
+ }
+ break;
+ }
+ }
+ };
+
public BackgroundJobsController(JobSchedulerService service) {
super(service);
@@ -73,11 +122,18 @@ public final class BackgroundJobsController extends StateController {
LocalServices.getService(ActivityManagerInternal.class));
mAppStateTracker = (AppStateTrackerImpl) Objects.requireNonNull(
LocalServices.getService(AppStateTracker.class));
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
@Override
public void startTrackingLocked() {
mAppStateTracker.addListener(mForceAppStandbyListener);
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ filter.addAction(Intent.ACTION_PACKAGE_UNSTOPPED);
+ filter.addDataScheme("package");
+ mContext.registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, filter, null, null);
}
@Override
@@ -99,11 +155,45 @@ public final class BackgroundJobsController extends StateController {
}
@Override
+ public void onAppRemovedLocked(String packageName, int uid) {
+ mPackageStoppedState.delete(uid, packageName);
+ }
+
+ @Override
+ public void onUserRemovedLocked(int userId) {
+ for (int u = mPackageStoppedState.numMaps() - 1; u >= 0; --u) {
+ final int uid = mPackageStoppedState.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mPackageStoppedState.deleteAt(u);
+ }
+ }
+ }
+
+ @Override
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
+ pw.println("Aconfig flags:");
+ pw.increaseIndent();
+ pw.print(android.content.pm.Flags.FLAG_STAY_STOPPED,
+ android.content.pm.Flags.stayStopped());
+ pw.println();
+ pw.decreaseIndent();
+ pw.println();
+
mAppStateTracker.dump(pw);
pw.println();
+ pw.println("Stopped packages:");
+ pw.increaseIndent();
+ mPackageStoppedState.forEach((uid, pkgName, isStopped) -> {
+ pw.print(uid);
+ pw.print(":");
+ pw.print(pkgName);
+ pw.print("=");
+ pw.println(isStopped);
+ });
+ pw.println();
+
mService.getJobStore().forEachJob(predicate, (jobStatus) -> {
final int uid = jobStatus.getSourceUid();
final String sourcePkg = jobStatus.getSourcePackageName();
@@ -205,14 +295,34 @@ public final class BackgroundJobsController extends StateController {
}
}
+ private boolean isPackageStopped(String packageName, int uid) {
+ if (mPackageStoppedState.contains(uid, packageName)) {
+ return mPackageStoppedState.get(uid, packageName);
+ }
+ final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
+ mPackageStoppedState.add(uid, packageName, isStopped);
+ return isStopped;
+ }
+
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus, final long nowElapsed,
int activeState) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean isUserBgRestricted =
- !mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
- && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName);
+ final boolean isSourcePkgStopped =
+ isPackageStopped(jobStatus.getSourcePackageName(), jobStatus.getSourceUid());
+ final boolean isCallingPkgStopped;
+ if (!jobStatus.isProxyJob()) {
+ isCallingPkgStopped = isSourcePkgStopped;
+ } else {
+ isCallingPkgStopped =
+ isPackageStopped(jobStatus.getCallingPackageName(), jobStatus.getUid());
+ }
+ final boolean isStopped = android.content.pm.Flags.stayStopped()
+ && (isCallingPkgStopped || isSourcePkgStopped);
+ final boolean isUserBgRestricted = isStopped
+ || (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled()
+ && !mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, packageName));
// If a job started with the foreground flag, it'll cause the UID to stay active
// and thus cause areJobsRestricted() to always return false, so if
// areJobsRestricted() returns false and the app is BG restricted and not TOP,
@@ -233,7 +343,8 @@ public final class BackgroundJobsController extends StateController {
&& isUserBgRestricted
&& mService.getUidProcState(uid)
> ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- final boolean canRun = !shouldStopImmediately
+ // Don't let jobs (including proxied jobs) run if the app is in the stopped state.
+ final boolean canRun = !isStopped && !shouldStopImmediately
&& !mAppStateTracker.areJobsRestricted(
uid, packageName, jobStatus.canRunInBatterySaver());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b74806494a60..d1f575ef40c8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1102,6 +1102,12 @@ public final class JobStatus {
return job.getService();
}
+ /** Return the package name of the app that scheduled the job. */
+ public String getCallingPackageName() {
+ return job.getService().getPackageName();
+ }
+
+ /** Return the package name of the app on whose behalf the job was scheduled. */
public String getSourcePackageName() {
return sourcePackageName;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 5c6c49a18caa..23a5d4d20a2e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2805,7 +2805,7 @@ public class Intent implements Parcelable, Cloneable {
* and the package in the stopped state cannot self-start for any reason unless there's an
* explicit request to start a component in the package. The {@link #ACTION_PACKAGE_UNSTOPPED}
* broadcast is sent when such an explicit process start occurs and the package is taken
- * out of the stopped state.
+ * out of the stopped state. The data contains the name of the package.
* </p>
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index cd3f0f0ca5b2..1da7f0c059b0 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -19,7 +19,6 @@ package com.android.server.content;
import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
-import android.content.pm.PackageManagerInternal;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
@@ -29,7 +28,6 @@ import android.util.SparseBooleanArray;
import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
public class SyncJobService extends JobService {
private static final String TAG = "SyncManager";
@@ -99,20 +97,6 @@ public class SyncJobService extends JobService {
return true;
}
- // TODO(b/209852664): remove this logic from here once it's added within JobScheduler.
- // JobScheduler should not call onStartJob for syncs whose source packages are stopped.
- // Until JS adds the relevant logic, this is a temporary solution to keep deferring syncs
- // for packages in the stopped state.
- if (android.content.pm.Flags.stayStopped()) {
- if (LocalServices.getService(PackageManagerInternal.class)
- .isPackageStopped(op.owningPackage, op.target.userId)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Slog.d(TAG, "Skipping sync for force-stopped package: " + op.owningPackage);
- }
- return false;
- }
- }
-
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
synchronized (sLock) {
final int jobId = params.getJobId();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
new file mode 100644
index 000000000000..cdae8c6ab004
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.job.JobInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.AppStateTracker;
+import com.android.server.AppStateTrackerImpl;
+import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobStore;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class BackgroundJobsControllerTest {
+ private static final int CALLING_UID = 1000;
+ private static final String CALLING_PACKAGE = "com.test.calling.package";
+ private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final int SOURCE_UID = 10001;
+ private static final int ALTERNATE_UID = 12345;
+ private static final String ALTERNATE_SOURCE_PACKAGE = "com.test.alternate.package";
+ private static final int SOURCE_USER_ID = 0;
+
+ private BackgroundJobsController mBackgroundJobsController;
+ private BroadcastReceiver mStoppedReceiver;
+ private JobStore mJobStore;
+
+ private MockitoSession mMockingSession;
+ @Mock
+ private Context mContext;
+ @Mock
+ private AppStateTrackerImpl mAppStateTrackerImpl;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private JobSchedulerService mJobSchedulerService;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(AppGlobals.class)
+ .mockStatic(LocalServices.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // Called in StateController constructor.
+ when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
+ when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
+ // Called in BackgroundJobsController constructor.
+ doReturn(mock(ActivityManagerInternal.class))
+ .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+ doReturn(mAppStateTrackerImpl)
+ .when(() -> LocalServices.getService(AppStateTracker.class));
+ doReturn(mPackageManagerInternal)
+ .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
+ when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
+ // Called in JobStatus constructor.
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+
+ doReturn(false).when(mAppStateTrackerImpl)
+ .areJobsRestricted(anyInt(), anyString(), anyBoolean());
+ doReturn(true).when(mAppStateTrackerImpl)
+ .isRunAnyInBackgroundAppOpsAllowed(anyInt(), anyString());
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ mBackgroundJobsController = new BackgroundJobsController(mJobSchedulerService);
+ mBackgroundJobsController.startTrackingLocked();
+
+ verify(mContext).registerReceiverAsUser(receiverCaptor.capture(), any(),
+ ArgumentMatchers.argThat(filter ->
+ filter.hasAction(Intent.ACTION_PACKAGE_RESTARTED)
+ && filter.hasAction(Intent.ACTION_PACKAGE_UNSTOPPED)),
+ any(), any());
+ mStoppedReceiver = receiverCaptor.getValue();
+
+ // Need to do this since we're using a mock JS and not a real object.
+ doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(SOURCE_UID);
+ doReturn(new ArraySet<>(new String[]{ALTERNATE_SOURCE_PACKAGE}))
+ .when(mJobSchedulerService).getPackagesForUidLocked(ALTERNATE_UID);
+ setPackageUid(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE);
+ setPackageUid(SOURCE_UID, SOURCE_PACKAGE);
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void setPackageUid(final int uid, final String pkgName) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
+ private void setStoppedState(int uid, String pkgName, boolean stopped) {
+ Intent intent = new Intent(
+ stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED);
+ intent.putExtra(Intent.EXTRA_UID, uid);
+ intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, pkgName, null));
+ mStoppedReceiver.onReceive(mContext, intent);
+ }
+
+ private void setUidBias(int uid, int bias) {
+ int prevBias = mJobSchedulerService.getUidBias(uid);
+ doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias);
+ }
+ }
+
+ private void trackJobs(JobStatus... jobs) {
+ for (JobStatus job : jobs) {
+ mJobStore.add(job);
+ synchronized (mBackgroundJobsController.mLock) {
+ mBackgroundJobsController.maybeStartTrackingJobLocked(job, null);
+ }
+ }
+ }
+
+ private JobInfo.Builder createBaseJobInfoBuilder(String pkgName, int jobId) {
+ final ComponentName cn = spy(new ComponentName(pkgName, "TestBJCJobService"));
+ doReturn("TestBJCJobService").when(cn).flattenToShortString();
+ return new JobInfo.Builder(jobId, cn);
+ }
+
+ private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
+ JobInfo jobInfo) {
+ JobStatus js = JobStatus.createFromJobInfo(
+ jobInfo, callingUid, packageName, SOURCE_USER_ID, "BJCTest", testTag);
+ js.serviceProcessName = "testProcess";
+ // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
+ js.setStandbyBucket(FREQUENT_INDEX);
+ return js;
+ }
+
+ @Test
+ public void testStopped_disabled() {
+ mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+
+ @Test
+ public void testStopped_enabled() {
+ mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED);
+ // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself.
+ JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID,
+ createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build());
+ // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself.
+ JobStatus directJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID,
+ createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build());
+ // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE.
+ JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build());
+ // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE.
+ JobStatus proxyJob2 = createJobStatus("testStopped",
+ ALTERNATE_SOURCE_PACKAGE, CALLING_UID,
+ createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build());
+
+ trackJobs(directJob1, directJob2, proxyJob1, proxyJob2);
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertFalse(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertFalse(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertTrue(proxyJob2.isUserBgRestricted());
+
+ setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false);
+ assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob1.isUserBgRestricted());
+ assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(directJob2.isUserBgRestricted());
+ assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob1.isUserBgRestricted());
+ assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
+ assertFalse(proxyJob2.isUserBgRestricted());
+ }
+}