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