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