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