Tests for max retries/redirects, ETag switches.
[android/platform/packages/providers/DownloadProvider.git] / tests / src / com / android / providers / downloads / PublicApiFunctionalTest.java
1 /*
2  * Copyright (C) 2010 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.providers.downloads;
18
19 import static android.app.DownloadManager.STATUS_FAILED;
20 import static android.app.DownloadManager.STATUS_PAUSED;
21 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
22 import static com.google.testing.littlemock.LittleMock.anyInt;
23 import static com.google.testing.littlemock.LittleMock.anyString;
24 import static com.google.testing.littlemock.LittleMock.atLeastOnce;
25 import static com.google.testing.littlemock.LittleMock.isA;
26 import static com.google.testing.littlemock.LittleMock.never;
27 import static com.google.testing.littlemock.LittleMock.times;
28 import static com.google.testing.littlemock.LittleMock.verify;
29 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
30 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
31 import static java.net.HttpURLConnection.HTTP_OK;
32 import static java.net.HttpURLConnection.HTTP_PARTIAL;
33 import static java.net.HttpURLConnection.HTTP_PRECON_FAILED;
34 import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
35
36 import android.app.DownloadManager;
37 import android.app.Notification;
38 import android.app.NotificationManager;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.database.Cursor;
42 import android.net.ConnectivityManager;
43 import android.net.Uri;
44 import android.os.Environment;
45 import android.os.SystemClock;
46 import android.provider.Downloads;
47 import android.test.suitebuilder.annotation.LargeTest;
48
49 import com.google.mockwebserver.MockResponse;
50 import com.google.mockwebserver.RecordedRequest;
51
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.FileNotFoundException;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.util.List;
58 import java.util.concurrent.TimeoutException;
59
60 @LargeTest
61 public class PublicApiFunctionalTest extends AbstractPublicApiTest {
62     private static final String REDIRECTED_PATH = "/other_path";
63     private static final String ETAG = "my_etag";
64
65     protected File mTestDirectory;
66     private NotificationManager mNotifManager;
67
68     public PublicApiFunctionalTest() {
69         super(new FakeSystemFacade());
70     }
71
72     @Override
73     protected void setUp() throws Exception {
74         super.setUp();
75
76         mNotifManager = (NotificationManager) getContext()
77                 .getSystemService(Context.NOTIFICATION_SERVICE);
78
79         mTestDirectory = new File(Environment.getExternalStorageDirectory() + File.separator
80                                   + "download_manager_functional_test");
81         if (mTestDirectory.exists()) {
82             for (File file : mTestDirectory.listFiles()) {
83                 file.delete();
84             }
85         } else {
86             mTestDirectory.mkdir();
87         }
88     }
89
90     @Override
91     protected void tearDown() throws Exception {
92         if (mTestDirectory != null && mTestDirectory.exists()) {
93             for (File file : mTestDirectory.listFiles()) {
94                 file.delete();
95             }
96             mTestDirectory.delete();
97         }
98         super.tearDown();
99     }
100
101     public void testBasicRequest() throws Exception {
102         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
103
104         Download download = enqueueRequest(getRequest());
105         assertEquals(DownloadManager.STATUS_PENDING,
106                      download.getLongField(DownloadManager.COLUMN_STATUS));
107         assertEquals(getServerUri(REQUEST_PATH),
108                      download.getStringField(DownloadManager.COLUMN_URI));
109         assertEquals(download.mId, download.getLongField(DownloadManager.COLUMN_ID));
110         assertEquals(mSystemFacade.currentTimeMillis(),
111                      download.getLongField(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
112
113         mSystemFacade.incrementTimeMillis(10);
114         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
115         RecordedRequest request = takeRequest();
116         assertEquals("GET", request.getMethod());
117         assertEquals(REQUEST_PATH, request.getPath());
118
119         Uri localUri = Uri.parse(download.getStringField(DownloadManager.COLUMN_LOCAL_URI));
120         assertEquals("content", localUri.getScheme());
121         checkUriContent(localUri);
122         assertEquals("text/plain", download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));
123
124         int size = FILE_CONTENT.length();
125         assertEquals(size, download.getLongField(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
126         assertEquals(size, download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
127         assertEquals(mSystemFacade.currentTimeMillis(),
128                      download.getLongField(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
129
130         checkCompleteDownload(download);
131     }
132
133     private void checkUriContent(Uri uri) throws FileNotFoundException, IOException {
134         InputStream inputStream = mResolver.openInputStream(uri);
135         try {
136             assertEquals(FILE_CONTENT, readStream(inputStream));
137         } finally {
138             inputStream.close();
139         }
140     }
141
142     public void testTitleAndDescription() throws Exception {
143         Download download = enqueueRequest(getRequest()
144                                            .setTitle("my title")
145                                            .setDescription("my description"));
146         assertEquals("my title", download.getStringField(DownloadManager.COLUMN_TITLE));
147         assertEquals("my description",
148                      download.getStringField(DownloadManager.COLUMN_DESCRIPTION));
149     }
150
151     public void testDownloadError() throws Exception {
152         enqueueResponse(buildEmptyResponse(HTTP_NOT_FOUND));
153         runSimpleFailureTest(HTTP_NOT_FOUND);
154     }
155
156     public void testUnhandledHttpStatus() throws Exception {
157         enqueueResponse(buildEmptyResponse(1234)); // some invalid HTTP status
158         runSimpleFailureTest(DownloadManager.ERROR_UNHANDLED_HTTP_CODE);
159     }
160
161     public void testInterruptedDownload() throws Exception {
162         int initialLength = 5;
163         enqueueInterruptedDownloadResponses(initialLength);
164
165         Download download = enqueueRequest(getRequest());
166         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
167         assertEquals(initialLength,
168                      download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
169         assertEquals(FILE_CONTENT.length(),
170                      download.getLongField(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
171         takeRequest(); // get the first request out of the queue
172
173         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
174         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
175         checkCompleteDownload(download);
176
177         List<String> headers = takeRequest().getHeaders();
178         assertTrue("No Range header: " + headers,
179                    headers.contains("Range: bytes=" + initialLength + "-"));
180         assertTrue("No ETag header: " + headers, headers.contains("If-Match: " + ETAG));
181     }
182
183     public void testInterruptedExternalDownload() throws Exception {
184         enqueueInterruptedDownloadResponses(5);
185         Download download = enqueueRequest(getRequest().setDestinationUri(getExternalUri()));
186         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
187         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
188         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
189         checkCompleteDownload(download);
190     }
191
192     private void enqueueInterruptedDownloadResponses(int initialLength) {
193         // the first response has normal headers but unexpectedly closes after initialLength bytes
194         enqueueResponse(buildPartialResponse(0, initialLength));
195         // the second response returns partial content for the rest of the data
196         enqueueResponse(buildPartialResponse(initialLength, FILE_CONTENT.length()));
197     }
198
199     private MockResponse buildPartialResponse(int start, int end) {
200         int totalLength = FILE_CONTENT.length();
201         boolean isFirstResponse = (start == 0);
202         int status = isFirstResponse ? HTTP_OK : HTTP_PARTIAL;
203         MockResponse response = buildResponse(status, FILE_CONTENT.substring(start, end))
204                 .setHeader("Content-length", totalLength)
205                 .setHeader("Etag", ETAG);
206         if (!isFirstResponse) {
207             response.setHeader(
208                     "Content-range", "bytes " + start + "-" + totalLength + "/" + totalLength);
209         }
210         return response;
211     }
212
213     // enqueue a huge response to keep the receiveing thread in DownloadThread.java busy for a while
214     // give enough time to do something (cancel/remove etc) on that downloadrequest
215     // while it is in progress
216     private MockResponse buildContinuingResponse() {
217         int numPackets = 100;
218         int contentLength = STRING_1K.length() * numPackets;
219         return buildResponse(HTTP_OK, STRING_1K)
220                .setHeader("Content-length", contentLength)
221                .setHeader("Etag", ETAG)
222                .setBytesPerSecond(1024);
223     }
224
225     public void testFiltering() throws Exception {
226         enqueueResponse(buildEmptyResponse(HTTP_OK));
227         enqueueResponse(buildEmptyResponse(HTTP_NOT_FOUND));
228
229         Download download1 = enqueueRequest(getRequest());
230         download1.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
231
232         mSystemFacade.incrementTimeMillis(1); // ensure downloads are correctly ordered by time
233         Download download2 = enqueueRequest(getRequest());
234         download2.runUntilStatus(DownloadManager.STATUS_FAILED);
235
236         mSystemFacade.incrementTimeMillis(1);
237         Download download3 = enqueueRequest(getRequest());
238
239         Cursor cursor = mManager.query(new DownloadManager.Query());
240         checkAndCloseCursor(cursor, download3, download2, download1);
241
242         cursor = mManager.query(new DownloadManager.Query().setFilterById(download2.mId));
243         checkAndCloseCursor(cursor, download2);
244
245         cursor = mManager.query(new DownloadManager.Query()
246                                 .setFilterByStatus(DownloadManager.STATUS_PENDING));
247         checkAndCloseCursor(cursor, download3);
248
249         cursor = mManager.query(new DownloadManager.Query()
250                                 .setFilterByStatus(DownloadManager.STATUS_FAILED
251                                               | DownloadManager.STATUS_SUCCESSFUL));
252         checkAndCloseCursor(cursor, download2, download1);
253
254         cursor = mManager.query(new DownloadManager.Query()
255                                 .setFilterByStatus(DownloadManager.STATUS_RUNNING));
256         checkAndCloseCursor(cursor);
257
258         mSystemFacade.incrementTimeMillis(1);
259         Download invisibleDownload = enqueueRequest(getRequest().setVisibleInDownloadsUi(false));
260         cursor = mManager.query(new DownloadManager.Query());
261         checkAndCloseCursor(cursor, invisibleDownload, download3, download2, download1);
262         cursor = mManager.query(new DownloadManager.Query().setOnlyIncludeVisibleInDownloadsUi(true));
263         checkAndCloseCursor(cursor, download3, download2, download1);
264     }
265
266     public void testOrdering() throws Exception {
267         enqueueResponse(buildResponse(HTTP_OK, "small contents"));
268         enqueueResponse(buildResponse(HTTP_OK, "large contents large contents"));
269         enqueueResponse(buildEmptyResponse(HTTP_NOT_FOUND));
270
271         Download download1 = enqueueRequest(getRequest());
272         download1.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
273
274         mSystemFacade.incrementTimeMillis(1);
275         Download download2 = enqueueRequest(getRequest());
276         download2.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
277
278         mSystemFacade.incrementTimeMillis(1);
279         Download download3 = enqueueRequest(getRequest());
280         download3.runUntilStatus(DownloadManager.STATUS_FAILED);
281
282         // default ordering -- by timestamp descending
283         Cursor cursor = mManager.query(new DownloadManager.Query());
284         checkAndCloseCursor(cursor, download3, download2, download1);
285
286         cursor = mManager.query(new DownloadManager.Query()
287                 .orderBy(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP,
288                         DownloadManager.Query.ORDER_ASCENDING));
289         checkAndCloseCursor(cursor, download1, download2, download3);
290
291         cursor = mManager.query(new DownloadManager.Query()
292                 .orderBy(DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
293                         DownloadManager.Query.ORDER_DESCENDING));
294         checkAndCloseCursor(cursor, download2, download1, download3);
295
296         cursor = mManager.query(new DownloadManager.Query()
297                 .orderBy(DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
298                         DownloadManager.Query.ORDER_ASCENDING));
299         checkAndCloseCursor(cursor, download3, download1, download2);
300     }
301
302     private void checkAndCloseCursor(Cursor cursor, Download... downloads) {
303         try {
304             int idIndex = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID);
305             assertEquals(downloads.length, cursor.getCount());
306             cursor.moveToFirst();
307             for (Download download : downloads) {
308                 assertEquals(download.mId, cursor.getLong(idIndex));
309                 cursor.moveToNext();
310             }
311         } finally {
312             cursor.close();
313         }
314     }
315
316     public void testInvalidUri() throws Exception {
317         try {
318             enqueueRequest(getRequest("/no_host"));
319         } catch (IllegalArgumentException exc) { // expected
320             return;
321         }
322
323         fail("No exception thrown for invalid URI");
324     }
325
326     public void testDestination() throws Exception {
327         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
328         Uri destination = getExternalUri();
329         Download download = enqueueRequest(getRequest().setDestinationUri(destination));
330         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
331
332         Uri localUri = Uri.parse(download.getStringField(DownloadManager.COLUMN_LOCAL_URI));
333         assertEquals(destination, localUri);
334
335         InputStream stream = new FileInputStream(destination.getPath());
336         try {
337             assertEquals(FILE_CONTENT, readStream(stream));
338         } finally {
339             stream.close();
340         }
341     }
342
343     private Uri getExternalUri() {
344         return Uri.fromFile(mTestDirectory).buildUpon().appendPath("testfile.txt").build();
345     }
346
347     public void testRequestHeaders() throws Exception {
348         enqueueResponse(buildEmptyResponse(HTTP_OK));
349         Download download = enqueueRequest(getRequest().addRequestHeader("Header1", "value1")
350                                            .addRequestHeader("Header2", "value2"));
351         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
352
353         List<String> headers = takeRequest().getHeaders();
354         assertTrue(headers.contains("Header1: value1"));
355         assertTrue(headers.contains("Header2: value2"));
356     }
357
358     public void testDelete() throws Exception {
359         Download download = enqueueRequest(getRequest().addRequestHeader("header", "value"));
360         mManager.remove(download.mId);
361         Cursor cursor = mManager.query(new DownloadManager.Query());
362         try {
363             assertEquals(0, cursor.getCount());
364         } finally {
365             cursor.close();
366         }
367     }
368
369     public void testSizeLimitOverMobile() throws Exception {
370         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
371         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
372
373         mSystemFacade.mMaxBytesOverMobile = (long) FILE_CONTENT.length() - 1;
374         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_MOBILE;
375         Download download = enqueueRequest(getRequest());
376         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
377
378         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
379         // first response was read, but aborted after the DL manager processed the Content-Length
380         // header, so we need to enqueue a second one
381         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
382     }
383
384     public void testRedirect301() throws Exception {
385         RecordedRequest lastRequest = runRedirectionTest(301);
386         // for 301, upon retry/resume, we reuse the redirected URI
387         assertEquals(REDIRECTED_PATH, lastRequest.getPath());
388     }
389
390     public void testRedirect302() throws Exception {
391         RecordedRequest lastRequest = runRedirectionTest(302);
392         // for 302, upon retry/resume, we use the original URI
393         assertEquals(REQUEST_PATH, lastRequest.getPath());
394     }
395
396     public void testRunawayRedirect() throws Exception {
397         for (int i = 0; i < 16; i++) {
398             enqueueResponse(buildEmptyResponse(HTTP_MOVED_TEMP)
399                     .setHeader("Location", mServer.getUrl("/" + i).toString()));
400         }
401
402         final Download download = enqueueRequest(getRequest());
403
404         // Ensure that we arrive at failed download, instead of spinning forever
405         download.runUntilStatus(DownloadManager.STATUS_FAILED);
406         assertEquals(DownloadManager.ERROR_TOO_MANY_REDIRECTS, download.getReason());
407     }
408
409     public void testRunawayUnavailable() throws Exception {
410         final int RETRY_DELAY = 120;
411         for (int i = 0; i < 16; i++) {
412             enqueueResponse(
413                     buildEmptyResponse(HTTP_UNAVAILABLE).setHeader("Retry-after", RETRY_DELAY));
414         }
415
416         final Download download = enqueueRequest(getRequest());
417         for (int i = 0; i < Constants.MAX_RETRIES - 1; i++) {
418             download.runUntilStatus(DownloadManager.STATUS_PAUSED);
419             mSystemFacade.incrementTimeMillis((RETRY_DELAY + 60) * SECOND_IN_MILLIS);
420         }
421
422         // Ensure that we arrive at failed download, instead of spinning forever
423         download.runUntilStatus(DownloadManager.STATUS_FAILED);
424     }
425
426     public void testNoEtag() throws Exception {
427         enqueueResponse(buildPartialResponse(0, 5).removeHeader("Etag"));
428         runSimpleFailureTest(DownloadManager.ERROR_CANNOT_RESUME);
429     }
430
431     public void testEtagChanged() throws Exception {
432         final String A = "kittenz";
433         final String B = "puppiez";
434
435         // 1. Try downloading A, but partial result
436         enqueueResponse(buildResponse(HTTP_OK, A.substring(0, 2))
437                 .setHeader("Content-length", A.length())
438                 .setHeader("Etag", A));
439
440         // 2. Try resuming A, but fail ETag check
441         enqueueResponse(buildEmptyResponse(HTTP_PRECON_FAILED));
442
443         final Download download = enqueueRequest(getRequest());
444         RecordedRequest req;
445
446         // 1. Try downloading A, but partial result
447         download.runUntilStatus(STATUS_PAUSED);
448         assertEquals(DownloadManager.PAUSED_WAITING_TO_RETRY, download.getReason());
449         req = takeRequest();
450         assertNull(getHeaderValue(req, "Range"));
451         assertNull(getHeaderValue(req, "If-Match"));
452
453         // 2. Try resuming A, but fail ETag check
454         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
455         download.runUntilStatus(STATUS_FAILED);
456         assertEquals(HTTP_PRECON_FAILED, download.getReason());
457         req = takeRequest();
458         assertEquals("bytes=2-", getHeaderValue(req, "Range"));
459         assertEquals(A, getHeaderValue(req, "If-Match"));
460     }
461
462     public void testSanitizeMediaType() throws Exception {
463         enqueueResponse(buildEmptyResponse(HTTP_OK)
464                 .setHeader("Content-Type", "text/html; charset=ISO-8859-4"));
465         Download download = enqueueRequest(getRequest());
466         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
467         assertEquals("text/html", download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));
468     }
469
470     public void testNoContentLength() throws Exception {
471         enqueueResponse(buildEmptyResponse(HTTP_OK).removeHeader("Content-length"));
472         runSimpleFailureTest(DownloadManager.ERROR_CANNOT_RESUME);
473     }
474
475     public void testInsufficientSpace() throws Exception {
476         // this would be better done by stubbing the system API to check available space, but in the
477         // meantime, just use an absurdly large header value
478         enqueueResponse(buildEmptyResponse(HTTP_OK)
479                 .setHeader("Content-Length", 1024L * 1024 * 1024 * 1024 * 1024));
480         runSimpleFailureTest(DownloadManager.ERROR_INSUFFICIENT_SPACE);
481     }
482
483     public void testCancel() throws Exception {
484         // return 'real time' from FakeSystemFacade so that DownloadThread will report progress
485         mSystemFacade.setReturnActualTime(true);
486         enqueueResponse(buildContinuingResponse());
487         Download download = enqueueRequest(getRequest());
488         // give the download time to get started and progress to 1% completion
489         // before cancelling it.
490         boolean rslt = download.runUntilProgress(1);
491         assertTrue(rslt);
492         mManager.remove(download.mId);
493
494         // Verify that row is removed from database
495         final long timeout = SystemClock.elapsedRealtime() + (15 * SECOND_IN_MILLIS);
496         while (download.getStatusIfExists() != -1) {
497             if (SystemClock.elapsedRealtime() > timeout) {
498                 throw new TimeoutException("Row wasn't removed");
499             }
500             SystemClock.sleep(100);
501         }
502     }
503
504     public void testDownloadCompleteBroadcast() throws Exception {
505         enqueueResponse(buildEmptyResponse(HTTP_OK));
506         Download download = enqueueRequest(getRequest());
507         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
508
509         assertEquals(1, mSystemFacade.mBroadcastsSent.size());
510         Intent broadcast = mSystemFacade.mBroadcastsSent.get(0);
511         assertEquals(DownloadManager.ACTION_DOWNLOAD_COMPLETE, broadcast.getAction());
512         assertEquals(PACKAGE_NAME, broadcast.getPackage());
513         long intentId = broadcast.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
514         assertEquals(download.mId, intentId);
515     }
516
517     public void testNotificationClickedBroadcast() throws Exception {
518         Download download = enqueueRequest(getRequest());
519
520         DownloadReceiver receiver = new DownloadReceiver();
521         receiver.mSystemFacade = mSystemFacade;
522         Intent intent = new Intent(Constants.ACTION_LIST);
523         intent.setData(Uri.parse(Downloads.Impl.CONTENT_URI + "/" + download.mId));
524         intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS,
525                 new long[] { download.mId });
526         receiver.onReceive(mContext, intent);
527
528         assertEquals(1, mSystemFacade.mBroadcastsSent.size());
529         Intent broadcast = mSystemFacade.mBroadcastsSent.get(0);
530         assertEquals(DownloadManager.ACTION_NOTIFICATION_CLICKED, broadcast.getAction());
531         assertEquals(PACKAGE_NAME, broadcast.getPackage());
532     }
533
534     public void testBasicConnectivityChanges() throws Exception {
535         enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
536
537         // without connectivity, download immediately pauses
538         mSystemFacade.mActiveNetworkType = null;
539         Download download = enqueueRequest(getRequest());
540         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
541
542         // connecting should start the download
543         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
544         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
545     }
546
547     public void testAllowedNetworkTypes() throws Exception {
548         enqueueResponse(buildEmptyResponse(HTTP_OK));
549         enqueueResponse(buildEmptyResponse(HTTP_OK));
550
551         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_MOBILE;
552
553         // by default, use any connection
554         Download download = enqueueRequest(getRequest());
555         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
556
557         // restrict a download to wifi...
558         download = enqueueRequest(getRequest()
559                                   .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI));
560         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
561         // ...then enable wifi
562         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
563         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
564     }
565
566     public void testRoaming() throws Exception {
567         enqueueResponse(buildEmptyResponse(HTTP_OK));
568         enqueueResponse(buildEmptyResponse(HTTP_OK));
569
570         mSystemFacade.mIsRoaming = true;
571
572         // by default, allow roaming
573         Download download = enqueueRequest(getRequest());
574         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
575
576         // disallow roaming for a download...
577         download = enqueueRequest(getRequest().setAllowedOverRoaming(false));
578         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
579         // ...then turn off roaming
580         mSystemFacade.mIsRoaming = false;
581         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
582     }
583
584     public void testContentObserver() throws Exception {
585         enqueueResponse(buildEmptyResponse(HTTP_OK));
586         mResolver.resetNotified();
587         final Download download = enqueueRequest(getRequest());
588         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
589         assertTrue(mResolver.mNotifyWasCalled);
590     }
591
592     public void testNotificationNever() throws Exception {
593         enqueueResponse(buildEmptyResponse(HTTP_OK));
594
595         final Download download = enqueueRequest(
596                 getRequest().setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN));
597         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
598
599         verify(mNotifManager, times(1)).cancelAll();
600         verify(mNotifManager, never()).notify(anyString(), anyInt(), isA(Notification.class));
601     }
602
603     public void testNotificationVisible() throws Exception {
604         enqueueResponse(buildEmptyResponse(HTTP_OK));
605
606         // only shows in-progress notifications
607         final Download download = enqueueRequest(getRequest());
608         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
609
610         // TODO: verify different notif types with tags
611         verify(mNotifManager, times(1)).cancelAll();
612         verify(mNotifManager, atLeastOnce()).notify(anyString(), anyInt(), isA(Notification.class));
613     }
614
615     public void testNotificationVisibleComplete() throws Exception {
616         enqueueResponse(buildEmptyResponse(HTTP_OK));
617
618         final Download download = enqueueRequest(getRequest().setNotificationVisibility(
619                 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED));
620         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
621
622         // TODO: verify different notif types with tags
623         verify(mNotifManager, times(1)).cancelAll();
624         verify(mNotifManager, atLeastOnce()).notify(anyString(), anyInt(), isA(Notification.class));
625     }
626
627     public void testRetryAfter() throws Exception {
628         final int delay = 120;
629         enqueueResponse(
630                 buildEmptyResponse(HTTP_UNAVAILABLE).setHeader("Retry-after", delay));
631         enqueueResponse(buildEmptyResponse(HTTP_OK));
632
633         Download download = enqueueRequest(getRequest());
634         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
635
636         // download manager adds random 0-30s offset
637         mSystemFacade.incrementTimeMillis((delay + 31) * 1000);
638         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
639     }
640
641     public void testManyInterruptions() throws Exception {
642         final int length = FILE_CONTENT.length();
643         for (int i = 0; i < length; i++) {
644             enqueueResponse(buildPartialResponse(i, i + 1));
645         }
646
647         Download download = enqueueRequest(getRequest());
648         for (int i = 0; i < length - 1; i++) {
649             download.runUntilStatus(DownloadManager.STATUS_PAUSED);
650             mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
651         }
652
653         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
654         checkCompleteDownload(download);
655     }
656
657     public void testExistingFile() throws Exception {
658         enqueueResponse(buildEmptyResponse(HTTP_OK));
659
660         // download a file which already exists.
661         // downloadservice should simply create filename with "-" and a number attached
662         // at the end; i.e., download shouldnot fail.
663         Uri destination = getExternalUri();
664         new File(destination.getPath()).createNewFile();
665
666         Download download = enqueueRequest(getRequest().setDestinationUri(destination));
667         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
668     }
669
670     public void testEmptyFields() throws Exception {
671         Download download = enqueueRequest(getRequest());
672         assertEquals("", download.getStringField(DownloadManager.COLUMN_TITLE));
673         assertEquals("", download.getStringField(DownloadManager.COLUMN_DESCRIPTION));
674         assertNull(download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));
675         assertEquals(0, download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
676         assertEquals(-1, download.getLongField(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
677         // just ensure no exception is thrown
678         download.getLongField(DownloadManager.COLUMN_REASON);
679     }
680
681     public void testRestart() throws Exception {
682         enqueueResponse(buildEmptyResponse(HTTP_NOT_FOUND));
683         enqueueResponse(buildEmptyResponse(HTTP_OK));
684
685         Download download = enqueueRequest(getRequest());
686         download.runUntilStatus(DownloadManager.STATUS_FAILED);
687
688         mManager.restartDownload(download.mId);
689         assertEquals(DownloadManager.STATUS_PENDING,
690                 download.getLongField(DownloadManager.COLUMN_STATUS));
691         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
692     }
693
694     private void checkCompleteDownload(Download download) throws Exception {
695         assertEquals(FILE_CONTENT.length(),
696                      download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
697         assertEquals(FILE_CONTENT, download.getContents());
698     }
699
700     private void runSimpleFailureTest(int expectedErrorCode) throws Exception {
701         Download download = enqueueRequest(getRequest());
702         download.runUntilStatus(DownloadManager.STATUS_FAILED);
703         assertEquals(expectedErrorCode,
704                      download.getLongField(DownloadManager.COLUMN_REASON));
705     }
706
707     /**
708      * Run a redirection test consisting of
709      * 1) Request to REQUEST_PATH with 3xx response redirecting to another URI
710      * 2) Request to REDIRECTED_PATH with interrupted partial response
711      * 3) Resume request to complete download
712      * @return the last request sent to the server, resuming after the interruption
713      */
714     private RecordedRequest runRedirectionTest(int status) throws Exception {
715         enqueueResponse(buildEmptyResponse(status)
716                 .setHeader("Location", mServer.getUrl(REDIRECTED_PATH).toString()));
717         enqueueInterruptedDownloadResponses(5);
718
719         final Download download = enqueueRequest(getRequest());
720         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
721         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
722         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
723
724         assertEquals(REQUEST_PATH, takeRequest().getPath());
725         assertEquals(REDIRECTED_PATH, takeRequest().getPath());
726
727         return takeRequest();
728     }
729
730     /**
731      * Return value of requested HTTP header, if it exists.
732      */
733     private static String getHeaderValue(RecordedRequest req, String header) {
734         header = header.toLowerCase() + ":";
735         for (String h : req.getHeaders()) {
736             if (h.toLowerCase().startsWith(header)) {
737                 return h.substring(header.length()).trim();
738             }
739         }
740         return null;
741     }
742 }