Serialize threading for download manager testing.
[android/platform/packages/providers/DownloadProvider.git] / tests / src / com / android / providers / downloads / PublicApiFunctionalTest.java
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.providers.downloads;
18
19 import android.content.Intent;
20 import android.database.Cursor;
21 import android.net.ConnectivityManager;
22 import android.net.DownloadManager;
23 import android.net.Uri;
24 import android.os.Environment;
25 import android.provider.Downloads;
26 import android.test.suitebuilder.annotation.LargeTest;
27 import tests.http.MockResponse;
28 import tests.http.RecordedRequest;
29
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.InputStream;
33 import java.net.MalformedURLException;
34 import java.util.List;
35
36 @LargeTest
37 public class PublicApiFunctionalTest extends AbstractPublicApiTest {
38     private static final int HTTP_NOT_ACCEPTABLE = 406;
39     private static final int HTTP_LENGTH_REQUIRED = 411;
40     private static final String REDIRECTED_PATH = "/other_path";
41     private static final String ETAG = "my_etag";
42
43     protected File mTestDirectory;
44
45     public PublicApiFunctionalTest() {
46         super(new FakeSystemFacade());
47     }
48
49     @Override
50     protected void setUp() throws Exception {
51         super.setUp();
52
53         mTestDirectory = new File(Environment.getExternalStorageDirectory() + File.separator
54                                   + "download_manager_functional_test");
55         if (mTestDirectory.exists()) {
56             mTestDirectory.delete();
57         }
58         if (!mTestDirectory.mkdir()) {
59             throw new RuntimeException("Couldn't create test directory: "
60                                        + mTestDirectory.getPath());
61         }
62     }
63
64     @Override
65     protected void tearDown() throws Exception {
66         if (mTestDirectory != null) {
67             for (File file : mTestDirectory.listFiles()) {
68                 file.delete();
69             }
70             mTestDirectory.delete();
71         }
72         super.tearDown();
73     }
74
75     public void testBasicRequest() throws Exception {
76         enqueueResponse(HTTP_OK, FILE_CONTENT);
77
78         Download download = enqueueRequest(getRequest());
79         assertEquals(DownloadManager.STATUS_PENDING,
80                      download.getLongField(DownloadManager.COLUMN_STATUS));
81         assertEquals(getServerUri(REQUEST_PATH),
82                      download.getStringField(DownloadManager.COLUMN_URI));
83         assertEquals(download.mId, download.getLongField(DownloadManager.COLUMN_ID));
84         assertEquals(mSystemFacade.currentTimeMillis(),
85                      download.getLongField(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
86
87         mSystemFacade.incrementTimeMillis(10);
88         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
89         RecordedRequest request = takeRequest();
90         assertEquals("GET", request.getMethod());
91         assertEquals(REQUEST_PATH, request.getPath());
92
93         Uri localUri = Uri.parse(download.getStringField(DownloadManager.COLUMN_LOCAL_URI));
94         assertEquals("file", localUri.getScheme());
95         assertStartsWith("//" + Environment.getDownloadCacheDirectory().getPath(),
96                          localUri.getSchemeSpecificPart());
97         assertEquals("text/plain", download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));
98
99         int size = FILE_CONTENT.length();
100         assertEquals(size, download.getLongField(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
101         assertEquals(size, download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
102         assertEquals(mSystemFacade.currentTimeMillis(),
103                      download.getLongField(DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP));
104
105         assertEquals(FILE_CONTENT, download.getContents());
106     }
107
108     public void testTitleAndDescription() throws Exception {
109         Download download = enqueueRequest(getRequest()
110                                            .setTitle("my title")
111                                            .setDescription("my description"));
112         assertEquals("my title", download.getStringField(DownloadManager.COLUMN_TITLE));
113         assertEquals("my description",
114                      download.getStringField(DownloadManager.COLUMN_DESCRIPTION));
115     }
116
117     public void testDownloadError() throws Exception {
118         enqueueEmptyResponse(HTTP_NOT_FOUND);
119         runSimpleFailureTest(HTTP_NOT_FOUND);
120     }
121
122     public void testUnhandledHttpStatus() throws Exception {
123         enqueueEmptyResponse(1234); // some invalid HTTP status
124         runSimpleFailureTest(DownloadManager.ERROR_UNHANDLED_HTTP_CODE);
125     }
126
127     public void testInterruptedDownload() throws Exception {
128         int initialLength = 5;
129         enqueueInterruptedDownloadResponses(initialLength);
130
131         Download download = enqueueRequest(getRequest());
132         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
133         assertEquals(initialLength,
134                      download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
135         assertEquals(FILE_CONTENT.length(),
136                      download.getLongField(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
137         takeRequest(); // get the first request out of the queue
138
139         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
140         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
141         assertEquals(FILE_CONTENT.length(),
142                      download.getLongField(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
143         assertEquals(FILE_CONTENT, download.getContents());
144
145         List<String> headers = takeRequest().getHeaders();
146         assertTrue("No Range header: " + headers,
147                    headers.contains("Range: bytes=" + initialLength + "-"));
148         assertTrue("No ETag header: " + headers, headers.contains("If-Match: " + ETAG));
149     }
150
151     private void enqueueInterruptedDownloadResponses(int initialLength) {
152         int totalLength = FILE_CONTENT.length();
153         // the first response has normal headers but unexpectedly closes after initialLength bytes
154         enqueuePartialResponse(initialLength);
155         // the second response returns partial content for the rest of the data
156         enqueueResponse(HTTP_PARTIAL_CONTENT, FILE_CONTENT.substring(initialLength))
157                 .addHeader("Content-range",
158                            "bytes " + initialLength + "-" + totalLength + "/" + totalLength)
159                 .addHeader("Etag", ETAG);
160     }
161
162     private MockResponse enqueuePartialResponse(int initialLength) {
163         return enqueueResponse(HTTP_OK, FILE_CONTENT.substring(0, initialLength))
164                                .addHeader("Content-length", FILE_CONTENT.length())
165                                .addHeader("Etag", ETAG)
166                                .setCloseConnectionAfter(true);
167     }
168
169     public void testFiltering() throws Exception {
170         enqueueEmptyResponse(HTTP_OK);
171         Download download1 = enqueueRequest(getRequest());
172         download1.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
173         enqueueEmptyResponse(HTTP_NOT_FOUND);
174
175         mSystemFacade.incrementTimeMillis(1); // ensure downloads are correctly ordered by time
176         Download download2 = enqueueRequest(getRequest());
177         download2.runUntilStatus(DownloadManager.STATUS_FAILED);
178
179         mSystemFacade.incrementTimeMillis(1);
180         Download download3 = enqueueRequest(getRequest());
181
182         Cursor cursor = mManager.query(new DownloadManager.Query());
183         checkAndCloseCursor(cursor, download3, download2, download1);
184
185         cursor = mManager.query(new DownloadManager.Query().setFilterById(download2.mId));
186         checkAndCloseCursor(cursor, download2);
187
188         cursor = mManager.query(new DownloadManager.Query()
189                                 .setFilterByStatus(DownloadManager.STATUS_PENDING));
190         checkAndCloseCursor(cursor, download3);
191
192         cursor = mManager.query(new DownloadManager.Query()
193                                 .setFilterByStatus(DownloadManager.STATUS_FAILED
194                                               | DownloadManager.STATUS_SUCCESSFUL));
195         checkAndCloseCursor(cursor, download2, download1);
196
197         cursor = mManager.query(new DownloadManager.Query()
198                                 .setFilterByStatus(DownloadManager.STATUS_RUNNING));
199         checkAndCloseCursor(cursor);
200     }
201
202     private void checkAndCloseCursor(Cursor cursor, Download... downloads) {
203         try {
204             int idIndex = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID);
205             assertEquals(downloads.length, cursor.getCount());
206             cursor.moveToFirst();
207             for (Download download : downloads) {
208                 assertEquals(download.mId, cursor.getLong(idIndex));
209                 cursor.moveToNext();
210             }
211         } finally {
212             cursor.close();
213         }
214     }
215
216     public void testInvalidUri() throws Exception {
217         try {
218             enqueueRequest(getRequest("/no_host"));
219         } catch (IllegalArgumentException exc) { // expected
220             return;
221         }
222
223         fail("No exception thrown for invalid URI");
224     }
225
226     public void testDestination() throws Exception {
227         enqueueResponse(HTTP_OK, FILE_CONTENT);
228         Uri destination = Uri.fromFile(mTestDirectory).buildUpon().appendPath("testfile").build();
229         Download download = enqueueRequest(getRequest().setDestinationUri(destination));
230         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
231
232         Uri localUri = Uri.parse(download.getStringField(DownloadManager.COLUMN_LOCAL_URI));
233         assertEquals(destination, localUri);
234
235         InputStream stream = new FileInputStream(destination.getSchemeSpecificPart());
236         try {
237             assertEquals(FILE_CONTENT, readStream(stream));
238         } finally {
239             stream.close();
240         }
241     }
242
243     public void testRequestHeaders() throws Exception {
244         enqueueEmptyResponse(HTTP_OK);
245         Download download = enqueueRequest(getRequest().setRequestHeader("Header1", "value1")
246                                            .setRequestHeader("Header2", "value2"));
247         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
248
249         List<String> headers = takeRequest().getHeaders();
250         assertTrue(headers.contains("Header1: value1"));
251         assertTrue(headers.contains("Header2: value2"));
252     }
253
254     public void testDelete() throws Exception {
255         Download download = enqueueRequest(getRequest().setRequestHeader("header", "value"));
256         mManager.remove(download.mId);
257         Cursor cursor = mManager.query(new DownloadManager.Query());
258         try {
259             assertEquals(0, cursor.getCount());
260         } finally {
261             cursor.close();
262         }
263     }
264
265     public void testSizeLimitOverMobile() throws Exception {
266         mSystemFacade.mMaxBytesOverMobile = FILE_CONTENT.length() - 1;
267
268         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_MOBILE;
269         enqueueResponse(HTTP_OK, FILE_CONTENT);
270         Download download = enqueueRequest(getRequest());
271         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
272
273         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
274         // first response was read, but aborted after the DL manager processed the Content-Length
275         // header, so we need to enqueue a second one
276         enqueueResponse(HTTP_OK, FILE_CONTENT);
277         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
278     }
279
280     public void testRedirect301() throws Exception {
281         RecordedRequest lastRequest = runRedirectionTest(301);
282         // for 301, upon retry, we reuse the redirected URI
283         assertEquals(REDIRECTED_PATH, lastRequest.getPath());
284     }
285
286     // TODO: currently fails
287     public void disabledTestRedirect302() throws Exception {
288         RecordedRequest lastRequest = runRedirectionTest(302);
289         // for 302, upon retry, we use the original URI
290         assertEquals(REQUEST_PATH, lastRequest.getPath());
291     }
292
293     public void testNoEtag() throws Exception {
294         enqueuePartialResponse(5).removeHeader("Etag");
295         runSimpleFailureTest(HTTP_LENGTH_REQUIRED);
296     }
297
298     public void testSanitizeMediaType() throws Exception {
299         enqueueEmptyResponse(HTTP_OK).addHeader("Content-Type", "text/html; charset=ISO-8859-4");
300         Download download = enqueueRequest(getRequest());
301         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
302         assertEquals("text/html", download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));
303     }
304
305     public void testNoContentLength() throws Exception {
306         enqueueEmptyResponse(HTTP_OK).removeHeader("Content-Length");
307         runSimpleFailureTest(HTTP_LENGTH_REQUIRED);
308     }
309
310     public void testNoContentType() throws Exception {
311         enqueueResponse(HTTP_OK, "").removeHeader("Content-Type");
312         runSimpleFailureTest(HTTP_NOT_ACCEPTABLE);
313     }
314
315     public void testInsufficientSpace() throws Exception {
316         // this would be better done by stubbing the system API to check available space, but in the
317         // meantime, just use an absurdly large header value
318         enqueueEmptyResponse(HTTP_OK).addHeader("Content-Length",
319                                                 1024L * 1024 * 1024 * 1024 * 1024);
320         runSimpleFailureTest(DownloadManager.ERROR_INSUFFICIENT_SPACE);
321     }
322
323     public void testCancel() throws Exception {
324         enqueuePartialResponse(5);
325         Download download = enqueueRequest(getRequest());
326         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
327
328         mManager.remove(download.mId);
329         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
330         runService();
331         // if the cancel didn't work, we should get an unexpected request to the HTTP server
332     }
333
334     public void testDownloadCompleteBroadcast() throws Exception {
335         enqueueEmptyResponse(HTTP_OK);
336         Download download = enqueueRequest(getRequest());
337         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
338
339         assertEquals(1, mSystemFacade.mBroadcastsSent.size());
340         Intent broadcast = mSystemFacade.mBroadcastsSent.get(0);
341         assertEquals(DownloadManager.ACTION_DOWNLOAD_COMPLETE, broadcast.getAction());
342         assertEquals(PACKAGE_NAME, broadcast.getPackage());
343         long intentId = broadcast.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
344         assertEquals(download.mId, intentId);
345     }
346
347     public void testNotificationClickedBroadcast() throws Exception {
348         Download download = enqueueRequest(getRequest().setShowNotification(
349                 DownloadManager.Request.NOTIFICATION_WHEN_RUNNING));
350
351         DownloadReceiver receiver = new DownloadReceiver();
352         receiver.mSystemFacade = mSystemFacade;
353         Intent intent = new Intent(Constants.ACTION_LIST);
354         intent.setData(Uri.parse(Downloads.Impl.CONTENT_URI + "/" + download.mId));
355         receiver.onReceive(mContext, intent);
356
357         assertEquals(1, mSystemFacade.mBroadcastsSent.size());
358         Intent broadcast = mSystemFacade.mBroadcastsSent.get(0);
359         assertEquals(DownloadManager.ACTION_NOTIFICATION_CLICKED, broadcast.getAction());
360         assertEquals(PACKAGE_NAME, broadcast.getPackage());
361     }
362
363     public void testBasicConnectivityChanges() throws Exception {
364         enqueueResponse(HTTP_OK, FILE_CONTENT);
365         Download download = enqueueRequest(getRequest());
366
367         // without connectivity, download immediately pauses
368         mSystemFacade.mActiveNetworkType = null;
369         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
370
371         // connecting should start the download
372         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
373         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
374     }
375
376     public void testAllowedNetworkTypes() throws Exception {
377         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_MOBILE;
378
379         // by default, use any connection
380         enqueueEmptyResponse(HTTP_OK);
381         Download download = enqueueRequest(getRequest());
382         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
383
384         // restrict a download to wifi...
385         download = enqueueRequest(getRequest()
386                                   .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI));
387         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
388         // ...then enable wifi
389         mSystemFacade.mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
390         enqueueEmptyResponse(HTTP_OK);
391         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
392     }
393
394     public void testRoaming() throws Exception {
395         mSystemFacade.mIsRoaming = true;
396
397         // by default, allow roaming
398         enqueueEmptyResponse(HTTP_OK);
399         Download download = enqueueRequest(getRequest());
400         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
401
402         // disallow roaming for a download...
403         download = enqueueRequest(getRequest().setAllowedOverRoaming(false));
404         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
405         // ...then turn off roaming
406         mSystemFacade.mIsRoaming = false;
407         enqueueEmptyResponse(HTTP_OK);
408         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
409     }
410
411     public void testContentObserver() throws Exception {
412         enqueueEmptyResponse(HTTP_OK);
413         enqueueRequest(getRequest());
414         mResolver.resetNotified();
415         runService();
416         assertTrue(mResolver.mNotifyWasCalled);
417     }
418
419     public void testNotifications() throws Exception {
420         enqueueEmptyResponse(HTTP_OK);
421         Download download = enqueueRequest(getRequest()); // no visibility requested
422         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
423         assertEquals(0, mSystemFacade.mActiveNotifications.size());
424         assertEquals(0, mSystemFacade.mCanceledNotifications.size());
425
426         enqueueEmptyResponse(HTTP_OK);
427         download = enqueueRequest(
428                 getRequest()
429                 .setShowNotification(DownloadManager.Request.NOTIFICATION_WHEN_RUNNING));
430         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
431         assertEquals(1, mSystemFacade.mActiveNotifications.size());
432
433         // The notification doesn't actually get canceled until the UpdateThread runs again, which
434         // gets triggered by the DownloadThread updating the status in the provider.
435         runService();
436         assertEquals(0, mSystemFacade.mActiveNotifications.size());
437         assertEquals(1, mSystemFacade.mCanceledNotifications.size());
438     }
439
440     public void testRetryAfter() throws Exception {
441         final int delay = 120;
442         enqueueEmptyResponse(HTTP_SERVICE_UNAVAILABLE).addHeader("Retry-after", delay);
443         Download download = enqueueRequest(getRequest());
444         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
445
446         // download manager adds random 0-30s offset
447         mSystemFacade.incrementTimeMillis((delay + 31) * 1000);
448
449         enqueueEmptyResponse(HTTP_OK);
450         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
451     }
452
453     private void runSimpleFailureTest(int expectedErrorCode) throws Exception {
454         Download download = enqueueRequest(getRequest());
455         download.runUntilStatus(DownloadManager.STATUS_FAILED);
456         assertEquals(expectedErrorCode,
457                      download.getLongField(DownloadManager.COLUMN_ERROR_CODE));
458     }
459
460     /**
461      * Run a redirection test consisting of
462      * 1) Request to REQUEST_PATH with 3xx response redirecting to another URI
463      * 2) Request to REDIRECTED_PATH with interrupted partial response
464      * 3) Resume request to complete download
465      * @return the last request sent to the server, resuming after the interruption
466      */
467     private RecordedRequest runRedirectionTest(int status)
468             throws MalformedURLException, Exception {
469         enqueueEmptyResponse(status).addHeader("Location",
470                                                mServer.getUrl(REDIRECTED_PATH).toString());
471         enqueueInterruptedDownloadResponses(5);
472
473         Download download = enqueueRequest(getRequest());
474         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
475         assertEquals(REQUEST_PATH, takeRequest().getPath());
476
477         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
478         download.runUntilStatus(DownloadManager.STATUS_PAUSED);
479         assertEquals(REDIRECTED_PATH, takeRequest().getPath());
480
481         mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS);
482         download.runUntilStatus(DownloadManager.STATUS_SUCCESSFUL);
483         return takeRequest();
484     }
485 }