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