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 org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.when;
21 
22 import android.app.DownloadManager;
23 import android.app.NotificationManager;
24 import android.app.job.JobParameters;
25 import android.app.job.JobScheduler;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.pm.ProviderInfo;
29 import android.database.ContentObserver;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.provider.Downloads;
33 import android.test.MoreAsserts;
34 import android.test.RenamingDelegatingContext;
35 import android.test.ServiceTestCase;
36 import android.test.mock.MockContentResolver;
37 import android.util.Log;
38 
39 import com.google.mockwebserver.MockResponse;
40 import com.google.mockwebserver.MockWebServer;
41 import com.google.mockwebserver.RecordedRequest;
42 import com.google.mockwebserver.SocketPolicy;
43 
44 import java.io.BufferedReader;
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.InputStreamReader;
49 import java.net.MalformedURLException;
50 import java.net.UnknownHostException;
51 
52 public abstract class AbstractDownloadProviderFunctionalTest extends
53         ServiceTestCase<DownloadJobService> {
54 
55     protected static final String LOG_TAG = "DownloadProviderFunctionalTest";
56     private static final String PROVIDER_AUTHORITY = "downloads";
57     protected static final long RETRY_DELAY_MILLIS = 61 * 1000;
58 
59     protected static final String
60             FILE_CONTENT = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
61 
62     private final MockitoHelper mMockitoHelper = new MockitoHelper();
63 
64     protected MockWebServer mServer;
65     protected MockContentResolverWithNotify mResolver;
66     protected TestContext mTestContext;
67     protected FakeSystemFacade mSystemFacade;
68     protected static String STRING_1K;
69     static {
70         StringBuilder buff = new StringBuilder();
71         for (int i = 0; i < 1024; i++) {
72             buff.append("a" + i % 26);
73         }
74         STRING_1K = buff.toString();
75     }
76 
77     static class MockContentResolverWithNotify extends MockContentResolver {
78         public boolean mNotifyWasCalled = false;
79 
MockContentResolverWithNotify(Context context)80         public MockContentResolverWithNotify(Context context) {
81             super(context);
82         }
83 
resetNotified()84         public synchronized void resetNotified() {
85             mNotifyWasCalled = false;
86         }
87 
88         @Override
notifyChange( Uri uri, ContentObserver observer, boolean syncToNetwork)89         public synchronized void notifyChange(
90                 Uri uri, ContentObserver observer, boolean syncToNetwork) {
91             mNotifyWasCalled = true;
92         }
93     }
94 
95     /**
96      * Context passed to the provider and the service.  Allows most methods to pass through to the
97      * real Context (this is a LargeTest), with a few exceptions, including renaming file operations
98      * to avoid file and DB conflicts (via RenamingDelegatingContext).
99      */
100     static class TestContext extends RenamingDelegatingContext {
101         private static final String FILENAME_PREFIX = "test.";
102 
103         private final ContentResolver mResolver;
104         private final NotificationManager mNotifManager;
105         private final DownloadManager mDownloadManager;
106         private final JobScheduler mJobScheduler;
107 
TestContext(Context realContext)108         public TestContext(Context realContext) {
109             super(realContext, FILENAME_PREFIX);
110             mResolver = new MockContentResolverWithNotify(this);
111             mNotifManager = mock(NotificationManager.class);
112             mDownloadManager = mock(DownloadManager.class);
113             mJobScheduler = mock(JobScheduler.class);
114         }
115 
116         /**
117          * Direct DownloadService to our test instance of DownloadProvider.
118          */
119         @Override
getContentResolver()120         public ContentResolver getContentResolver() {
121             return mResolver;
122         }
123 
124         /**
125          * Stub some system services, allow access to others, and block the rest.
126          */
127         @Override
getSystemService(String name)128         public Object getSystemService(String name) {
129             if (Context.NOTIFICATION_SERVICE.equals(name)) {
130                 return mNotifManager;
131             } else if (Context.DOWNLOAD_SERVICE.equals(name)) {
132                 return mDownloadManager;
133             } else if (Context.JOB_SCHEDULER_SERVICE.equals(name)) {
134                 return mJobScheduler;
135             }
136 
137             return super.getSystemService(name);
138         }
139     }
140 
AbstractDownloadProviderFunctionalTest(FakeSystemFacade systemFacade)141     public AbstractDownloadProviderFunctionalTest(FakeSystemFacade systemFacade) {
142         super(DownloadJobService.class);
143         mSystemFacade = systemFacade;
144     }
145 
146     @Override
setUp()147     protected void setUp() throws Exception {
148         super.setUp();
149         mMockitoHelper.setUp(getClass());
150 
151         // Since we're testing a system app, AppDataDirGuesser doesn't find our
152         // cache dir, so set it explicitly.
153         System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
154 
155         final Context realContext = getContext();
156 
157         mTestContext = new TestContext(realContext);
158         mResolver = (MockContentResolverWithNotify) mTestContext.getContentResolver();
159 
160         final DownloadProvider provider = new DownloadProvider();
161         provider.mSystemFacade = mSystemFacade;
162 
163         ProviderInfo info = new ProviderInfo();
164         info.authority = "downloads";
165         provider.attachInfo(mTestContext, info);
166 
167         mResolver.addProvider(PROVIDER_AUTHORITY, provider);
168 
169         setContext(mTestContext);
170         setupService();
171         Helpers.setSystemFacade(mSystemFacade);
172 
173         mSystemFacade.setUp();
174         assertTrue(isDatabaseEmpty()); // ensure we're not messing with real data
175         assertTrue(isDatabaseSecureAgainstBadSelection());
176         mServer = new MockWebServer();
177         mServer.play();
178     }
179 
180     @Override
tearDown()181     protected void tearDown() throws Exception {
182         cleanUpDownloads();
183         mServer.shutdown();
184         mMockitoHelper.tearDown();
185         super.tearDown();
186     }
187 
startDownload(long id)188     protected void startDownload(long id) {
189         final JobParameters params = mock(JobParameters.class);
190         when(params.getJobId()).thenReturn((int) id);
191         getService().onStartJob(params);
192     }
193 
isDatabaseEmpty()194     private boolean isDatabaseEmpty() {
195         Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
196                 null, null, null, null);
197         try {
198             return cursor.getCount() == 0;
199         } finally {
200             cursor.close();
201         }
202     }
203 
isDatabaseSecureAgainstBadSelection()204     private boolean isDatabaseSecureAgainstBadSelection() {
205         Cursor cursor = null;
206         try {
207             cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null,
208                     "('1'='1'))) ORDER BY lastmod DESC--", null, null);
209         }
210         catch (Exception e) {
211             return true;
212         } finally {
213             if (cursor != null) {
214                 cursor.close();
215             }
216         }
217 
218         return false;
219     }
220 
221     /**
222      * Remove any downloaded files and delete any lingering downloads.
223      */
cleanUpDownloads()224     void cleanUpDownloads() {
225         if (mResolver == null) {
226             return;
227         }
228         String[] columns = new String[] {Downloads.Impl._DATA};
229         Cursor cursor = mResolver.query(Downloads.Impl.CONTENT_URI, columns, null, null, null);
230         try {
231             for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
232                 String filePath = cursor.getString(0);
233                 if (filePath == null) continue;
234                 Log.d(LOG_TAG, "Deleting " + filePath);
235                 new File(filePath).delete();
236             }
237         } finally {
238             cursor.close();
239         }
240         mResolver.delete(Downloads.Impl.CONTENT_URI, null, null);
241     }
242 
enqueueResponse(MockResponse resp)243     void enqueueResponse(MockResponse resp) {
244         mServer.enqueue(resp);
245     }
246 
buildResponse(int status, String body)247     MockResponse buildResponse(int status, String body) {
248         return new MockResponse().setResponseCode(status).setBody(body)
249                 .setHeader("Content-type", "text/plain")
250                 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
251     }
252 
buildResponse(int status, byte[] body)253     MockResponse buildResponse(int status, byte[] body) {
254         return new MockResponse().setResponseCode(status).setBody(body)
255                 .setHeader("Content-type", "text/plain")
256                 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
257     }
258 
buildEmptyResponse(int status)259     MockResponse buildEmptyResponse(int status) {
260         return buildResponse(status, "");
261     }
262 
263     /**
264      * Fetch the last request received by the MockWebServer.
265      */
takeRequest()266     protected RecordedRequest takeRequest() throws InterruptedException {
267         RecordedRequest request = mServer.takeRequest();
268         assertNotNull("Expected request was not made", request);
269         return request;
270     }
271 
getServerUri(String path)272     String getServerUri(String path) throws MalformedURLException, UnknownHostException {
273         return mServer.getUrl(path).toString();
274     }
275 
readStream(InputStream inputStream)276     protected String readStream(InputStream inputStream) throws IOException {
277         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
278         try {
279             char[] buffer = new char[1024];
280             int length = reader.read(buffer);
281             assertTrue("Failed to read anything from input stream", length > -1);
282             return String.valueOf(buffer, 0, length);
283         } finally {
284             reader.close();
285         }
286     }
287 
assertStartsWith(String expectedPrefix, String actual)288     protected void assertStartsWith(String expectedPrefix, String actual) {
289         String regex = "^" + expectedPrefix + ".*";
290         MoreAsserts.assertMatchesRegex(regex, actual);
291     }
292 }
293