1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.downloads;
18 
19 import static android.app.DownloadManager.STATUS_FAILED;
20 import static android.app.DownloadManager.STATUS_PAUSED;
21 import static android.net.TrafficStats.GB_IN_BYTES;
22 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
23 
24 import static org.mockito.Matchers.anyInt;
25 import static org.mockito.Matchers.anyString;
26 import static org.mockito.Matchers.isA;
27 import static org.mockito.Mockito.atLeastOnce;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.times;
30 import static org.mockito.Mockito.verify;
31 
32 import static java.net.HttpURLConnection.HTTP_MOVED_TEMP;
33 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
34 import static java.net.HttpURLConnection.HTTP_OK;
35 import static java.net.HttpURLConnection.HTTP_PARTIAL;
36 import static java.net.HttpURLConnection.HTTP_PRECON_FAILED;
37 import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
38 
39 import android.app.DownloadManager;
40 import android.app.Notification;
41 import android.app.NotificationManager;
42 import android.content.Intent;
43 import android.database.Cursor;
44 import android.net.ConnectivityManager;
45 import android.net.Uri;
46 import android.os.Environment;
47 import android.os.SystemClock;
48 import android.provider.Downloads;
49 import android.test.suitebuilder.annotation.LargeTest;
50 import android.test.suitebuilder.annotation.Suppress;
51 import android.text.format.DateUtils;
52 
53 import libcore.io.IoUtils;
54 
55 import com.google.mockwebserver.MockResponse;
56 import com.google.mockwebserver.RecordedRequest;
57 import com.google.mockwebserver.SocketPolicy;
58 
59 import java.io.File;
60 import java.io.FileInputStream;
61 import java.io.FileNotFoundException;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.util.List;
65 import java.util.concurrent.TimeUnit;
66 import java.util.concurrent.TimeoutException;
67 
68 @LargeTest
69 public 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 
PublicApiFunctionalTest()77     public PublicApiFunctionalTest() {
78         super(new FakeSystemFacade());
79     }
80 
81     @Override
setUp()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
tearDown()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 
testBasicRequest()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
testExtremelyLarge()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 
checkUriContent(Uri uri)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 
testTitleAndDescription()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 
testDownloadError()174     public void testDownloadError() throws Exception {
175         enqueueResponse(buildEmptyResponse(HTTP_NOT_FOUND));
176         runSimpleFailureTest(HTTP_NOT_FOUND);
177     }
178 
testUnhandledHttpStatus()179     public void testUnhandledHttpStatus() throws Exception {
180         enqueueResponse(buildEmptyResponse(1234)); // some invalid HTTP status
181         runSimpleFailureTest(DownloadManager.ERROR_UNHANDLED_HTTP_CODE);
182     }
183 
testInterruptedDownload()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 
testInterruptedExternalDownload()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 
enqueueInterruptedDownloadResponses(int initialLength)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 
buildPartialResponse(int start, int end)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
buildContinuingResponse()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 
testFiltering()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 
testOrdering()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 
checkAndCloseCursor(Cursor cursor, Download... downloads)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 
testInvalidUri()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 
testDestination()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 
getExternalUri()366     private Uri getExternalUri() {
367         return Uri.fromFile(mTestDirectory).buildUpon().appendPath("testfile.txt").build();
368     }
369 
testRequestHeaders()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 
testDelete()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 
testSizeLimitOverMobile()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 
testRedirect301()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 
testRedirect302()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 
testRunawayRedirect()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 
testRunawayUnavailable()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 
testNoEtag()451     public void testNoEtag() throws Exception {
452         enqueueResponse(buildPartialResponse(0, 5).removeHeader("Etag"));
453         runSimpleFailureTest(DownloadManager.ERROR_CANNOT_RESUME);
454     }
455 
testEtagChanged()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 
testSanitizeMediaType()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 
testNoContentLength()495     public void testNoContentLength() throws Exception {
496         enqueueResponse(buildEmptyResponse(HTTP_OK).removeHeader("Content-length"));
497         runSimpleFailureTest(DownloadManager.ERROR_CANNOT_RESUME);
498     }
499 
testInsufficientSpace()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 
testCancel()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 
testDownloadCompleteBroadcast()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 
testNotificationClickedBroadcast()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 
testNotificationCancelDownloadClicked()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 
testBasicConnectivityChanges()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 
testAllowedNetworkTypes()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 
testRoaming()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 
testContentObserver()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
testNotificationNever()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
testNotificationVisible()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
testNotificationVisibleComplete()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 
testRetryAfter()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 
testManyInterruptions()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 
testExistingFile()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 
testEmptyFields()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 
testRestart()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 
checkCompleteDownload(Download download)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 
runSimpleFailureTest(int expectedErrorCode)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      */
runRedirectionTest(int status)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      */
getHeaderValue(RecordedRequest req, String header)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