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