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