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 android.support.v4.app;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.fail;
21 
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.net.Uri;
26 import android.os.Parcel;
27 import android.os.Parcelable;
28 import android.support.annotation.Nullable;
29 import android.support.test.InstrumentationRegistry;
30 import android.support.test.filters.MediumTest;
31 import android.support.test.runner.AndroidJUnit4;
32 import android.util.Log;
33 
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.util.ArrayList;
39 import java.util.concurrent.CountDownLatch;
40 import java.util.concurrent.TimeUnit;
41 
42 @RunWith(AndroidJUnit4.class)
43 public class JobIntentServiceTest {
44     static final String TAG = "JobIntentServiceTest";
45 
46     static final int JOB_ID = 0x1000;
47 
48     static final Object sLock = new Object();
49     static CountDownLatch sReadyToRunLatch;
50     static CountDownLatch sServiceFinishedLatch;
51 
52     static boolean sFinished;
53     static ArrayList<Intent> sFinishedWork;
54     static String sFinishedErrorMsg;
55     static String sLastServiceState;
56 
57     Context mContext;
58 
59     @Before
setup()60     public void setup() {
61         mContext = InstrumentationRegistry.getContext();
62     }
63 
64     public static final class TestIntentItem implements Parcelable {
65         public final Intent intent;
66         public final TestIntentItem[] subitems;
67         public final Uri[] requireUrisGranted;
68         public final Uri[] requireUrisNotGranted;
69 
TestIntentItem(Intent intent)70         public TestIntentItem(Intent intent) {
71             this.intent = intent;
72             subitems = null;
73             requireUrisGranted = null;
74             requireUrisNotGranted = null;
75         }
76 
TestIntentItem(Intent intent, TestIntentItem[] subitems)77         public TestIntentItem(Intent intent, TestIntentItem[] subitems) {
78             this.intent = intent;
79             this.subitems = subitems;
80             intent.putExtra("subitems", subitems);
81             requireUrisGranted = null;
82             requireUrisNotGranted = null;
83         }
84 
TestIntentItem(Intent intent, Uri[] requireUrisGranted, Uri[] requireUrisNotGranted)85         public TestIntentItem(Intent intent, Uri[] requireUrisGranted,
86                 Uri[] requireUrisNotGranted) {
87             this.intent = intent;
88             subitems = null;
89             this.requireUrisGranted = requireUrisGranted;
90             this.requireUrisNotGranted = requireUrisNotGranted;
91         }
92 
93         @Override
toString()94         public String toString() {
95             StringBuilder sb = new StringBuilder(64);
96             sb.append("TestIntentItem { ");
97             sb.append(intent);
98             sb.append(" }");
99             return sb.toString();
100         }
101 
102         @Override
describeContents()103         public int describeContents() {
104             return 0;
105         }
106 
107         @Override
writeToParcel(Parcel parcel, int flags)108         public void writeToParcel(Parcel parcel, int flags) {
109             intent.writeToParcel(parcel, flags);
110             parcel.writeTypedArray(subitems, flags);
111         }
112 
TestIntentItem(Parcel parcel)113         TestIntentItem(Parcel parcel) {
114             intent = Intent.CREATOR.createFromParcel(parcel);
115             subitems = parcel.createTypedArray(CREATOR);
116             requireUrisGranted = null;
117             requireUrisNotGranted = null;
118         }
119 
120         public static final Parcelable.Creator<TestIntentItem> CREATOR =
121                 new Parcelable.Creator<TestIntentItem>() {
122 
123                     public TestIntentItem createFromParcel(Parcel source) {
124                         return new TestIntentItem(source);
125                     }
126 
127                     public TestIntentItem[] newArray(int size) {
128                         return new TestIntentItem[size];
129                     }
130                 };
131     }
132 
initStatics()133     static void initStatics() {
134         synchronized (sLock) {
135             sReadyToRunLatch = new CountDownLatch(1);
136             sServiceFinishedLatch = new CountDownLatch(1);
137             sFinished = false;
138             sFinishedWork = null;
139             sFinishedErrorMsg = null;
140         }
141     }
142 
allowServiceToRun()143     static void allowServiceToRun() {
144         sReadyToRunLatch.countDown();
145     }
146 
finishServiceExecution(ArrayList<Intent> work, String errorMsg)147     static void finishServiceExecution(ArrayList<Intent> work, String errorMsg) {
148         synchronized (sLock) {
149             if (!sFinished) {
150                 sFinishedWork = work;
151                 sFinishedErrorMsg = errorMsg;
152                 sServiceFinishedLatch.countDown();
153             }
154         }
155     }
156 
updateServiceState(String msg)157     static void updateServiceState(String msg) {
158         synchronized (sLock) {
159             sLastServiceState = msg;
160         }
161     }
162 
waitServiceFinish()163     void waitServiceFinish() {
164         try {
165             if (!sServiceFinishedLatch.await(10, TimeUnit.SECONDS)) {
166                 synchronized (sLock) {
167                     if (sFinishedErrorMsg != null) {
168                         fail("Timed out waiting for finish, service state " + sLastServiceState
169                                 + ", had error: " + sFinishedErrorMsg);
170                     }
171                     fail("Timed out waiting for finish, service state " + sLastServiceState);
172                 }
173             }
174         } catch (InterruptedException e) {
175             fail("Interrupted waiting for service to finish: " + e);
176         }
177         synchronized (sLock) {
178             if (sFinishedErrorMsg != null) {
179                 fail(sFinishedErrorMsg);
180             }
181         }
182     }
183 
184     public static class TargetService extends JobIntentService {
185         final ArrayList<Intent> mReceivedWork = new ArrayList<>();
186 
187         @Override
onCreate()188         public void onCreate() {
189             super.onCreate();
190             updateServiceState("Creating: " + this);
191             Log.i(TAG, "Creating: " + this);
192             Log.i(TAG, "Waiting for ready to run...");
193             try {
194                 if (!sReadyToRunLatch.await(10, TimeUnit.SECONDS)) {
195                     finishServiceExecution(null, "Timeout waiting for ready");
196                 }
197             } catch (InterruptedException e) {
198                 finishServiceExecution(null, "Interrupted waiting for ready: " + e);
199             }
200             updateServiceState("Past ready to run");
201             Log.i(TAG, "Running!");
202         }
203 
204         @Override
onHandleWork(@ullable Intent intent)205         protected void onHandleWork(@Nullable Intent intent) {
206             Log.i(TAG, "Handling work: " + intent);
207             updateServiceState("Handling work: " + intent);
208             mReceivedWork.add(intent);
209             intent.setExtrasClassLoader(TestIntentItem.class.getClassLoader());
210             Parcelable[] subitems = intent.getParcelableArrayExtra("subitems");
211             if (subitems != null) {
212                 for (Parcelable pitem : subitems) {
213                     JobIntentService.enqueueWork(this, TargetService.class,
214                             JOB_ID, ((TestIntentItem) pitem).intent);
215                 }
216             }
217         }
218 
219         @Override
onDestroy()220         public void onDestroy() {
221             Log.i(TAG, "Destroying: " + this);
222             updateServiceState("Destroying: " + this);
223             finishServiceExecution(mReceivedWork, null);
224             super.onDestroy();
225         }
226     }
227 
intentEquals(Intent i1, Intent i2)228     private boolean intentEquals(Intent i1, Intent i2) {
229         if (i1 == i2) {
230             return true;
231         }
232         if (i1 == null || i2 == null) {
233             return false;
234         }
235         return i1.filterEquals(i2);
236     }
237 
compareIntents(TestIntentItem[] expected, ArrayList<Intent> received)238     private void compareIntents(TestIntentItem[] expected, ArrayList<Intent> received) {
239         if (received == null) {
240             fail("Didn't receive any expected work.");
241         }
242         ArrayList<TestIntentItem> expectedArray = new ArrayList<>();
243         for (int i = 0; i < expected.length; i++) {
244             expectedArray.add(expected[i]);
245         }
246 
247         ComponentName serviceComp = new ComponentName(mContext, TargetService.class.getName());
248 
249         for (int i = 0; i < received.size(); i++) {
250             Intent r = received.get(i);
251             if (i < expected.length && expected[i].subitems != null) {
252                 TestIntentItem[] sub = expected[i].subitems;
253                 for (int j = 0; j < sub.length; j++) {
254                     expectedArray.add(sub[j]);
255                 }
256             }
257             if (i >= expectedArray.size()) {
258                 fail("Received more than " + expected.length + " work items, first extra is "
259                         + r);
260             }
261             if (r.getComponent() != null) {
262                 // Intents we get back from the compat service will have a component... make
263                 // sure that is correct, and then erase it so the intentEquals() will pass.
264                 assertEquals(serviceComp, r.getComponent());
265                 r.setComponent(null);
266             }
267             if (!intentEquals(r, expectedArray.get(i).intent)) {
268                 fail("Received intent #" + i + " " + r + " but expected " + expected[i]);
269             }
270         }
271         if (received.size() < expected.length) {
272             fail("Received only " + received.size() + " work items, but expected "
273                     + expected.length);
274         }
275     }
276 
277     /**
278      * Test simple case of enqueueing one piece of work.
279      */
280     @MediumTest
281     @Test
testEnqueueOne()282     public void testEnqueueOne() throws Throwable {
283         initStatics();
284 
285         TestIntentItem[] items = new TestIntentItem[] {
286                 new TestIntentItem(new Intent("FIRST")),
287         };
288 
289         for (TestIntentItem item : items) {
290             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
291         }
292         allowServiceToRun();
293 
294         waitServiceFinish();
295         compareIntents(items, sFinishedWork);
296     }
297 
298     /**
299      * Test case of enqueueing multiple pieces of work.
300      */
301     @MediumTest
302     @Test
testEnqueueMultiple()303     public void testEnqueueMultiple() throws Throwable {
304         initStatics();
305 
306         TestIntentItem[] items = new TestIntentItem[] {
307                 new TestIntentItem(new Intent("FIRST")),
308                 new TestIntentItem(new Intent("SECOND")),
309                 new TestIntentItem(new Intent("THIRD")),
310                 new TestIntentItem(new Intent("FOURTH")),
311         };
312 
313         for (TestIntentItem item : items) {
314             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
315         }
316         allowServiceToRun();
317 
318         waitServiceFinish();
319         compareIntents(items, sFinishedWork);
320     }
321 
322     /**
323      * Test case of enqueueing multiple pieces of work.
324      */
325     @MediumTest
326     @Test
testEnqueueSubWork()327     public void testEnqueueSubWork() throws Throwable {
328         initStatics();
329 
330         TestIntentItem[] items = new TestIntentItem[] {
331                 new TestIntentItem(new Intent("FIRST")),
332                 new TestIntentItem(new Intent("SECOND")),
333                 new TestIntentItem(new Intent("THIRD"), new TestIntentItem[] {
334                         new TestIntentItem(new Intent("FIFTH")),
335                         new TestIntentItem(new Intent("SIXTH")),
336                         new TestIntentItem(new Intent("SEVENTH")),
337                         new TestIntentItem(new Intent("EIGTH")),
338                 }),
339                 new TestIntentItem(new Intent("FOURTH")),
340         };
341 
342         for (TestIntentItem item : items) {
343             JobIntentService.enqueueWork(mContext, TargetService.class, JOB_ID, item.intent);
344         }
345         allowServiceToRun();
346 
347         waitServiceFinish();
348         compareIntents(items, sFinishedWork);
349     }
350 }
351