• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 androidx.core.app;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.fail;
21 
22 import android.app.job.JobScheduler;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.net.Uri;
27 import android.os.Build;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.support.test.InstrumentationRegistry;
31 import android.support.test.filters.MediumTest;
32 import android.support.test.runner.AndroidJUnit4;
33 import android.util.Log;
34 
35 import androidx.annotation.Nullable;
36 import androidx.annotation.RequiresApi;
37 
38 import org.junit.Before;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.util.ArrayList;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.TimeUnit;
45 
46 @RunWith(AndroidJUnit4.class)
47 public class JobIntentServiceTest {
48     static final String TAG = "JobIntentServiceTest";
49 
50     static final int JOB_ID = 0x1000;
51 
52     static final Object sLock = new Object();
53     static CountDownLatch sReadyToRunLatch;
54     static CountDownLatch sServiceWaitingLatch;
55     static CountDownLatch sServiceStoppedLatch;
56     static CountDownLatch sWaitCompleteLatch;
57     static CountDownLatch sServiceFinishedLatch;
58 
59     static boolean sFinished;
60     static ArrayList<Intent> sFinishedWork;
61     static String sFinishedErrorMsg;
62     static String sLastServiceState;
63 
64     Context mContext;
65 
66     @Before
setup()67     public void setup() {
68         mContext = InstrumentationRegistry.getContext();
69     }
70 
71     public static final class TestIntentItem implements Parcelable {
72         public static final int FLAG_WAIT = 1 << 0;
73         public static final int FLAG_STOPPED_AFTER_WAIT = 1 << 1;
74 
75         public final Intent intent;
76         public final TestIntentItem[] subitems;
77         public final int flags;
78         public final Uri[] requireUrisGranted;
79         public final Uri[] requireUrisNotGranted;
80 
TestIntentItem(Intent intent)81         public TestIntentItem(Intent intent) {
82             this.intent = intent;
83             subitems = null;
84             flags = 0;
85             requireUrisGranted = null;
86             requireUrisNotGranted = null;
87         }
88 
TestIntentItem(Intent intent, int flags)89         public TestIntentItem(Intent intent, int flags) {
90             this.intent = intent;
91             subitems = null;
92             this.flags = flags;
93             intent.putExtra("flags", flags);
94             requireUrisGranted = null;
95             requireUrisNotGranted = null;
96         }
97 
TestIntentItem(Intent intent, TestIntentItem[] subitems)98         public TestIntentItem(Intent intent, TestIntentItem[] subitems) {
99             this.intent = intent;
100             this.subitems = subitems;
101             intent.putExtra("subitems", subitems);
102             flags = 0;
103             requireUrisGranted = null;
104             requireUrisNotGranted = null;
105         }
106 
TestIntentItem(Intent intent, Uri[] requireUrisGranted, Uri[] requireUrisNotGranted)107         public TestIntentItem(Intent intent, Uri[] requireUrisGranted,
108                 Uri[] requireUrisNotGranted) {
109             this.intent = intent;
110             subitems = null;
111             flags = 0;
112             this.requireUrisGranted = requireUrisGranted;
113             this.requireUrisNotGranted = requireUrisNotGranted;
114         }
115 
116         @Override
toString()117         public String toString() {
118             StringBuilder sb = new StringBuilder(64);
119             sb.append("TestIntentItem { ");
120             sb.append(intent);
121             sb.append(" }");
122             return sb.toString();
123         }
124 
125         @Override
describeContents()126         public int describeContents() {
127             return 0;
128         }
129 
130         @Override
writeToParcel(Parcel parcel, int flags)131         public void writeToParcel(Parcel parcel, int flags) {
132             intent.writeToParcel(parcel, flags);
133             parcel.writeTypedArray(subitems, flags);
134             parcel.writeInt(flags);
135         }
136 
TestIntentItem(Parcel parcel)137         TestIntentItem(Parcel parcel) {
138             intent = Intent.CREATOR.createFromParcel(parcel);
139             subitems = parcel.createTypedArray(CREATOR);
140             flags = parcel.readInt();
141             requireUrisGranted = null;
142             requireUrisNotGranted = null;
143         }
144 
145         public static final Parcelable.Creator<TestIntentItem> CREATOR =
146                 new Parcelable.Creator<TestIntentItem>() {
147 
148                     public TestIntentItem createFromParcel(Parcel source) {
149                         return new TestIntentItem(source);
150                     }
151 
152                     public TestIntentItem[] newArray(int size) {
153                         return new TestIntentItem[size];
154                     }
155                 };
156     }
157 
initStatics()158     static void initStatics() {
159         synchronized (sLock) {
160             sReadyToRunLatch = new CountDownLatch(1);
161             sServiceWaitingLatch = new CountDownLatch(1);
162             sServiceStoppedLatch = new CountDownLatch(1);
163             sWaitCompleteLatch = new CountDownLatch(1);
164             sServiceFinishedLatch = new CountDownLatch(1);
165             sFinished = false;
166             sFinishedWork = null;
167             sFinishedErrorMsg = null;
168         }
169     }
170 
allowServiceToRun()171     static void allowServiceToRun() {
172         sReadyToRunLatch.countDown();
173     }
174 
serviceReportWaiting()175     static void serviceReportWaiting() {
176         sServiceWaitingLatch.countDown();
177     }
178 
ensureServiceWaiting()179     static void ensureServiceWaiting() {
180         try {
181             if (!sServiceWaitingLatch.await(10, TimeUnit.SECONDS)) {
182                 fail("Timed out waiting for wait, service state " + sLastServiceState);
183             }
184         } catch (InterruptedException e) {
185             fail("Interrupted waiting for service to wait: " + e);
186         }
187     }
188 
serviceReportStopped()189     static void serviceReportStopped() {
190         sServiceStoppedLatch.countDown();
191     }
192 
ensureServiceStopped()193     static void ensureServiceStopped() {
194         try {
195             if (!sServiceStoppedLatch.await(10, TimeUnit.SECONDS)) {
196                 fail("Timed out waiting for stop, service state " + sLastServiceState);
197             }
198         } catch (InterruptedException e) {
199             fail("Interrupted waiting for service to stop: " + e);
200         }
201     }
202 
allowServiceToResumeFromWait()203     static void allowServiceToResumeFromWait() {
204         sWaitCompleteLatch.countDown();
205     }
206 
finishServiceExecution(ArrayList<Intent> work, String errorMsg)207     static void finishServiceExecution(ArrayList<Intent> work, String errorMsg) {
208         synchronized (sLock) {
209             if (!sFinished) {
210                 sFinishedWork = work;
211                 sFinishedErrorMsg = errorMsg;
212                 sServiceFinishedLatch.countDown();
213             }
214         }
215     }
216 
updateServiceState(String msg)217     static void updateServiceState(String msg) {
218         synchronized (sLock) {
219             sLastServiceState = msg;
220         }
221     }
222 
waitServiceFinish()223     void waitServiceFinish() {
224         try {
225             if (!sServiceFinishedLatch.await(10, TimeUnit.SECONDS)) {
226                 synchronized (sLock) {
227                     if (sFinishedErrorMsg != null) {
228                         fail("Timed out waiting for finish, service state " + sLastServiceState
229                                 + ", had error: " + sFinishedErrorMsg);
230                     }
231                     fail("Timed out waiting for finish, service state " + sLastServiceState);
232                 }
233             }
234         } catch (InterruptedException e) {
235             fail("Interrupted waiting for service to finish: " + e);
236         }
237         synchronized (sLock) {
238             if (sFinishedErrorMsg != null) {
239                 fail(sFinishedErrorMsg);
240             }
241         }
242     }
243 
244     public static class TargetService extends JobIntentService {
245         final ArrayList<Intent> mReceivedWork = new ArrayList<>();
246 
247         @Override
onCreate()248         public void onCreate() {
249             super.onCreate();
250             updateServiceState("Creating: " + this);
251             Log.i(TAG, "Creating: " + this);
252             Log.i(TAG, "Waiting for ready to run...");
253             try {
254                 if (!sReadyToRunLatch.await(10, TimeUnit.SECONDS)) {
255                     finishServiceExecution(null, "Timeout waiting for ready");
256                 }
257             } catch (InterruptedException e) {
258                 finishServiceExecution(null, "Interrupted waiting for ready: " + e);
259             }
260             updateServiceState("Past ready to run");
261             Log.i(TAG, "Running!");
262         }
263 
264         @Override
onHandleWork(@ullable Intent intent)265         protected void onHandleWork(@Nullable Intent intent) {
266             Log.i(TAG, "Handling work: " + intent);
267             updateServiceState("Handling work: " + intent);
268             mReceivedWork.add(intent);
269             intent.setExtrasClassLoader(TestIntentItem.class.getClassLoader());
270             int flags = intent.getIntExtra("flags", 0);
271             if ((flags & TestIntentItem.FLAG_WAIT) != 0) {
272                 serviceReportWaiting();
273                 try {
274                     if (!sWaitCompleteLatch.await(10, TimeUnit.SECONDS)) {
275                         finishServiceExecution(null, "Timeout waiting for wait complete");
276                     }
277                 } catch (InterruptedException e) {
278                     finishServiceExecution(null, "Interrupted waiting for wait complete: " + e);
279                 }
280                 if ((flags & TestIntentItem.FLAG_STOPPED_AFTER_WAIT) != 0) {
281                     if (!isStopped()) {
282                         finishServiceExecution(null, "Service not stopped after waiting");
283                     }
284                 }
285             }
286             Parcelable[] subitems = intent.getParcelableArrayExtra("subitems");
287             if (subitems != null) {
288                 for (Parcelable pitem : subitems) {
289                     JobIntentService.enqueueWork(this, TargetService.class,
290                             JOB_ID, ((TestIntentItem) pitem).intent);
291                 }
292             }
293         }
294 
295         @Override
onStopCurrentWork()296         public boolean onStopCurrentWork() {
297             serviceReportStopped();
298             return super.onStopCurrentWork();
299         }
300 
301         @Override
onDestroy()302         public void onDestroy() {
303             Log.i(TAG, "Destroying: " + this);
304             updateServiceState("Destroying: " + this);
305             finishServiceExecution(mReceivedWork, null);
306             super.onDestroy();
307         }
308     }
309 
intentEquals(Intent i1, Intent i2)310     private boolean intentEquals(Intent i1, Intent i2) {
311         if (i1 == i2) {
312             return true;
313         }
314         if (i1 == null || i2 == null) {
315             return false;
316         }
317         return i1.filterEquals(i2);
318     }
319 
compareIntents(TestIntentItem[] expected, ArrayList<Intent> received)320     private void compareIntents(TestIntentItem[] expected, ArrayList<Intent> received) {
321         if (received == null) {
322             fail("Didn't receive any expected work.");
323         }
324         ArrayList<TestIntentItem> expectedArray = new ArrayList<>();
325         for (int i = 0; i < expected.length; i++) {
326             expectedArray.add(expected[i]);
327         }
328 
329         ComponentName serviceComp = new ComponentName(mContext, TargetService.class.getName());
330 
331         for (int i = 0; i < received.size(); i++) {
332             Intent r = received.get(i);
333             if (i < expected.length && expected[i].subitems != null) {
334                 TestIntentItem[] sub = expected[i].subitems;
335                 for (int j = 0; j < sub.length; j++) {
336                     expectedArray.add(sub[j]);
337                 }
338             }
339             if (i >= expectedArray.size()) {
340                 fail("Received more than " + expected.length + " work items, first extra is "
341                         + r);
342             }
343             if (r.getComponent() != null) {
344                 // Intents we get back from the compat service will have a component... make
345                 // sure that is correct, and then erase it so the intentEquals() will pass.
346                 assertEquals(serviceComp, r.getComponent());
347                 r.setComponent(null);
348             }
349             if (!intentEquals(r, expectedArray.get(i).intent)) {
350                 fail("Received intent #" + i + " " + r + " but expected " + expected[i]);
351             }
352         }
353         if (received.size() < expected.length) {
354             fail("Received only " + received.size() + " work items, but expected "
355                     + expected.length);
356         }
357     }
358 
359     /**
360      * Test simple case of enqueueing one piece of work.
361      */
362     @MediumTest
363     @Test
testEnqueueOne()364     public void testEnqueueOne() throws Throwable {
365         initStatics();
366 
367         TestIntentItem[] items = new TestIntentItem[] {
368                 new TestIntentItem(new Intent("FIRST")),
369         };
370 
371         for (TestIntentItem item : items) {
372             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
373         }
374         allowServiceToRun();
375 
376         waitServiceFinish();
377         compareIntents(items, sFinishedWork);
378     }
379 
380     /**
381      * Test case of enqueueing multiple pieces of work.
382      */
383     @MediumTest
384     @Test
testEnqueueMultiple()385     public void testEnqueueMultiple() throws Throwable {
386         initStatics();
387 
388         TestIntentItem[] items = new TestIntentItem[] {
389                 new TestIntentItem(new Intent("FIRST")),
390                 new TestIntentItem(new Intent("SECOND")),
391                 new TestIntentItem(new Intent("THIRD")),
392                 new TestIntentItem(new Intent("FOURTH")),
393         };
394 
395         for (TestIntentItem item : items) {
396             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
397         }
398         allowServiceToRun();
399 
400         waitServiceFinish();
401         compareIntents(items, sFinishedWork);
402     }
403 
404     /**
405      * Test case of enqueueing multiple pieces of work.
406      */
407     @MediumTest
408     @Test
testEnqueueSubWork()409     public void testEnqueueSubWork() throws Throwable {
410         initStatics();
411 
412         TestIntentItem[] items = new TestIntentItem[] {
413                 new TestIntentItem(new Intent("FIRST")),
414                 new TestIntentItem(new Intent("SECOND")),
415                 new TestIntentItem(new Intent("THIRD"), new TestIntentItem[] {
416                         new TestIntentItem(new Intent("FIFTH")),
417                         new TestIntentItem(new Intent("SIXTH")),
418                         new TestIntentItem(new Intent("SEVENTH")),
419                         new TestIntentItem(new Intent("EIGTH")),
420                 }),
421                 new TestIntentItem(new Intent("FOURTH")),
422         };
423 
424         for (TestIntentItem item : items) {
425             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
426         }
427         allowServiceToRun();
428 
429         waitServiceFinish();
430         compareIntents(items, sFinishedWork);
431     }
432 
433     /**
434      * Test case of job stopping while it is doing work.
435      */
436     @MediumTest
437     @Test
438     @RequiresApi(26)
testStopWhileWorking()439     public void testStopWhileWorking() throws Throwable {
440         if (Build.VERSION.SDK_INT < 26) {
441             // This test only makes sense when running on top of JobScheduler.
442             return;
443         }
444 
445         initStatics();
446 
447         TestIntentItem[] items = new TestIntentItem[] {
448                 new TestIntentItem(new Intent("FIRST"),
449                         TestIntentItem.FLAG_WAIT | TestIntentItem.FLAG_STOPPED_AFTER_WAIT),
450         };
451 
452         for (TestIntentItem item : items) {
453             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
454         }
455         allowServiceToRun();
456         ensureServiceWaiting();
457 
458         // At this point we will make the job stop...  this isn't normally how this would
459         // happen with an IntentJobService, and doing it this way breaks re-delivery of
460         // work, but we have CTS tests for the underlying redlivery mechanism.
461         ((JobScheduler) mContext.getApplicationContext().getSystemService(
462                 Context.JOB_SCHEDULER_SERVICE)).cancel(JOB_ID);
463         ensureServiceStopped();
464 
465         allowServiceToResumeFromWait();
466 
467         waitServiceFinish();
468         compareIntents(items, sFinishedWork);
469     }
470 }
471