1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.job.controllers; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 23 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; 24 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED; 25 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.mockito.ArgumentMatchers.any; 30 import static org.mockito.ArgumentMatchers.anyBoolean; 31 import static org.mockito.ArgumentMatchers.anyInt; 32 import static org.mockito.ArgumentMatchers.anyLong; 33 import static org.mockito.ArgumentMatchers.anyString; 34 import static org.mockito.ArgumentMatchers.eq; 35 import static org.mockito.Mockito.spy; 36 import static org.mockito.Mockito.verify; 37 38 import android.app.ActivityManagerInternal; 39 import android.app.AppGlobals; 40 import android.app.job.JobInfo; 41 import android.content.BroadcastReceiver; 42 import android.content.ComponentName; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.content.pm.IPackageManager; 47 import android.content.pm.PackageManager; 48 import android.content.pm.PackageManagerInternal; 49 import android.net.Uri; 50 import android.os.UserHandle; 51 import android.platform.test.flag.junit.SetFlagsRule; 52 import android.util.ArraySet; 53 54 import androidx.test.runner.AndroidJUnit4; 55 56 import com.android.server.AppStateTracker; 57 import com.android.server.AppStateTrackerImpl; 58 import com.android.server.LocalServices; 59 import com.android.server.job.JobSchedulerService; 60 import com.android.server.job.JobStore; 61 62 import org.junit.After; 63 import org.junit.Before; 64 import org.junit.Rule; 65 import org.junit.Test; 66 import org.junit.runner.RunWith; 67 import org.mockito.ArgumentCaptor; 68 import org.mockito.ArgumentMatchers; 69 import org.mockito.Mock; 70 import org.mockito.MockitoSession; 71 import org.mockito.quality.Strictness; 72 73 @RunWith(AndroidJUnit4.class) 74 public class BackgroundJobsControllerTest { 75 private static final int CALLING_UID = 1000; 76 private static final String CALLING_PACKAGE = "com.test.calling.package"; 77 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; 78 private static final int SOURCE_UID = 10001; 79 private static final int ALTERNATE_UID = 12345; 80 private static final String ALTERNATE_SOURCE_PACKAGE = "com.test.alternate.package"; 81 private static final int SOURCE_USER_ID = 0; 82 83 private BackgroundJobsController mBackgroundJobsController; 84 private BroadcastReceiver mStoppedReceiver; 85 private JobStore mJobStore; 86 87 private MockitoSession mMockingSession; 88 @Mock 89 private Context mContext; 90 @Mock 91 private AppStateTrackerImpl mAppStateTrackerImpl; 92 @Mock 93 private IPackageManager mIPackageManager; 94 @Mock 95 private JobSchedulerService mJobSchedulerService; 96 @Mock 97 private PackageManagerInternal mPackageManagerInternal; 98 @Mock 99 private PackageManager mPackageManager; 100 101 @Rule 102 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); 103 104 @Before setUp()105 public void setUp() throws Exception { 106 mMockingSession = mockitoSession() 107 .initMocks(this) 108 .mockStatic(AppGlobals.class) 109 .mockStatic(LocalServices.class) 110 .strictness(Strictness.LENIENT) 111 .startMocking(); 112 113 // Called in StateController constructor. 114 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); 115 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); 116 // Called in BackgroundJobsController constructor. 117 doReturn(mock(ActivityManagerInternal.class)) 118 .when(() -> LocalServices.getService(ActivityManagerInternal.class)); 119 doReturn(mAppStateTrackerImpl) 120 .when(() -> LocalServices.getService(AppStateTracker.class)); 121 doReturn(mPackageManagerInternal) 122 .when(() -> LocalServices.getService(PackageManagerInternal.class)); 123 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); 124 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore); 125 // Called in JobStatus constructor. 126 doReturn(mIPackageManager).when(AppGlobals::getPackageManager); 127 128 doReturn(false).when(mAppStateTrackerImpl) 129 .areJobsRestricted(anyInt(), anyString(), anyBoolean()); 130 doReturn(true).when(mAppStateTrackerImpl) 131 .isRunAnyInBackgroundAppOpsAllowed(anyInt(), anyString()); 132 133 // Initialize real objects. 134 // Capture the listeners. 135 ArgumentCaptor<BroadcastReceiver> receiverCaptor = 136 ArgumentCaptor.forClass(BroadcastReceiver.class); 137 138 when(mContext.getPackageManager()).thenReturn(mPackageManager); 139 mBackgroundJobsController = new BackgroundJobsController(mJobSchedulerService); 140 mBackgroundJobsController.startTrackingLocked(); 141 142 verify(mContext).registerReceiverAsUser(receiverCaptor.capture(), any(), 143 ArgumentMatchers.argThat(filter -> 144 filter.hasAction(Intent.ACTION_PACKAGE_RESTARTED) 145 && filter.hasAction(Intent.ACTION_PACKAGE_UNSTOPPED)), 146 any(), any()); 147 mStoppedReceiver = receiverCaptor.getValue(); 148 149 // Need to do this since we're using a mock JS and not a real object. 150 doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE})) 151 .when(mJobSchedulerService).getPackagesForUidLocked(SOURCE_UID); 152 doReturn(new ArraySet<>(new String[]{ALTERNATE_SOURCE_PACKAGE})) 153 .when(mJobSchedulerService).getPackagesForUidLocked(ALTERNATE_UID); 154 setPackageUid(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE); 155 setPackageUid(SOURCE_UID, SOURCE_PACKAGE); 156 } 157 158 @After tearDown()159 public void tearDown() { 160 if (mMockingSession != null) { 161 mMockingSession.finishMocking(); 162 } 163 } 164 setPackageUid(final int uid, final String pkgName)165 private void setPackageUid(final int uid, final String pkgName) throws Exception { 166 doReturn(uid).when(mIPackageManager) 167 .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid))); 168 } 169 setStoppedState(int uid, String pkgName, boolean stopped)170 private void setStoppedState(int uid, String pkgName, boolean stopped) { 171 try { 172 doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid); 173 sendPackageStoppedBroadcast(uid, pkgName, stopped); 174 } catch (PackageManager.NameNotFoundException e) { 175 fail("Unable to set stopped state for unknown package: " + pkgName); 176 } 177 } 178 sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped)179 private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) { 180 Intent intent = new Intent( 181 stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED); 182 intent.putExtra(Intent.EXTRA_UID, uid); 183 intent.setData(Uri.fromParts(IntentFilter.SCHEME_PACKAGE, pkgName, null)); 184 mStoppedReceiver.onReceive(mContext, intent); 185 } 186 trackJobs(JobStatus... jobs)187 private void trackJobs(JobStatus... jobs) { 188 for (JobStatus job : jobs) { 189 mJobStore.add(job); 190 synchronized (mBackgroundJobsController.mLock) { 191 mBackgroundJobsController.maybeStartTrackingJobLocked(job, null); 192 } 193 } 194 } 195 createBaseJobInfoBuilder(String pkgName, int jobId)196 private JobInfo.Builder createBaseJobInfoBuilder(String pkgName, int jobId) { 197 final ComponentName cn = spy(new ComponentName(pkgName, "TestBJCJobService")); 198 doReturn("TestBJCJobService").when(cn).flattenToShortString(); 199 return new JobInfo.Builder(jobId, cn); 200 } 201 createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)202 private JobStatus createJobStatus(String testTag, String packageName, int callingUid, 203 JobInfo jobInfo) { 204 JobStatus js = JobStatus.createFromJobInfo( 205 jobInfo, callingUid, packageName, SOURCE_USER_ID, "BJCTest", testTag); 206 js.serviceProcessName = "testProcess"; 207 // Make sure tests aren't passing just because the default bucket is likely ACTIVE. 208 js.setStandbyBucket(FREQUENT_INDEX); 209 return js; 210 } 211 212 @Test testRestartedBroadcastWithoutStopping()213 public void testRestartedBroadcastWithoutStopping() { 214 mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED); 215 // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself. 216 JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID, 217 createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build()); 218 // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself. 219 JobStatus directJob2 = createJobStatus("testStopped", 220 ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID, 221 createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build()); 222 // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE. 223 JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID, 224 createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build()); 225 // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE. 226 JobStatus proxyJob2 = createJobStatus("testStopped", 227 ALTERNATE_SOURCE_PACKAGE, CALLING_UID, 228 createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build()); 229 230 trackJobs(directJob1, directJob2, proxyJob1, proxyJob2); 231 232 sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true); 233 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 234 assertFalse(directJob1.isUserBgRestricted()); 235 assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 236 assertFalse(directJob2.isUserBgRestricted()); 237 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 238 assertFalse(proxyJob1.isUserBgRestricted()); 239 assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 240 assertFalse(proxyJob2.isUserBgRestricted()); 241 242 sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false); 243 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 244 assertFalse(directJob1.isUserBgRestricted()); 245 assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 246 assertFalse(directJob2.isUserBgRestricted()); 247 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 248 assertFalse(proxyJob1.isUserBgRestricted()); 249 assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 250 assertFalse(proxyJob2.isUserBgRestricted()); 251 } 252 253 @Test testStopped_disabled()254 public void testStopped_disabled() { 255 mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED); 256 // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself. 257 JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID, 258 createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build()); 259 // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself. 260 JobStatus directJob2 = createJobStatus("testStopped", 261 ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID, 262 createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build()); 263 // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE. 264 JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID, 265 createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build()); 266 // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE. 267 JobStatus proxyJob2 = createJobStatus("testStopped", 268 ALTERNATE_SOURCE_PACKAGE, CALLING_UID, 269 createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build()); 270 271 trackJobs(directJob1, directJob2, proxyJob1, proxyJob2); 272 273 setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true); 274 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 275 assertFalse(directJob1.isUserBgRestricted()); 276 assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 277 assertFalse(directJob2.isUserBgRestricted()); 278 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 279 assertFalse(proxyJob1.isUserBgRestricted()); 280 assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 281 assertFalse(proxyJob2.isUserBgRestricted()); 282 283 setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false); 284 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 285 assertFalse(directJob1.isUserBgRestricted()); 286 assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 287 assertFalse(directJob2.isUserBgRestricted()); 288 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 289 assertFalse(proxyJob1.isUserBgRestricted()); 290 assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 291 assertFalse(proxyJob2.isUserBgRestricted()); 292 } 293 294 @Test testStopped_enabled()295 public void testStopped_enabled() { 296 mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED); 297 // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself. 298 JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID, 299 createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build()); 300 // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself. 301 JobStatus directJob2 = createJobStatus("testStopped", 302 ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID, 303 createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build()); 304 // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE. 305 JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID, 306 createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build()); 307 // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE. 308 JobStatus proxyJob2 = createJobStatus("testStopped", 309 ALTERNATE_SOURCE_PACKAGE, CALLING_UID, 310 createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build()); 311 312 trackJobs(directJob1, directJob2, proxyJob1, proxyJob2); 313 314 setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true); 315 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 316 assertFalse(directJob1.isUserBgRestricted()); 317 assertFalse(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 318 assertTrue(directJob2.isUserBgRestricted()); 319 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 320 assertFalse(proxyJob1.isUserBgRestricted()); 321 assertFalse(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 322 assertTrue(proxyJob2.isUserBgRestricted()); 323 324 setStoppedState(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false); 325 assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 326 assertFalse(directJob1.isUserBgRestricted()); 327 assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 328 assertFalse(directJob2.isUserBgRestricted()); 329 assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 330 assertFalse(proxyJob1.isUserBgRestricted()); 331 assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); 332 assertFalse(proxyJob2.isUserBgRestricted()); 333 } 334 } 335