/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.providers.downloads; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.DownloadManager; import android.app.NotificationManager; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.pm.ProviderInfo; import android.database.ContentObserver; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.MatrixCursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.provider.Downloads; import android.provider.MediaStore; import android.test.MoreAsserts; import android.test.RenamingDelegatingContext; import android.test.ServiceTestCase; import android.test.mock.MockContentProvider; import android.test.mock.MockContentResolver; import android.util.Log; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; import com.google.mockwebserver.SocketPolicy; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.UnknownHostException; public abstract class AbstractDownloadProviderFunctionalTest extends ServiceTestCase { protected static final String LOG_TAG = "DownloadProviderFunctionalTest"; private static final String PROVIDER_AUTHORITY = "downloads"; protected static final long RETRY_DELAY_MILLIS = 61 * 1000; protected static final String FILE_CONTENT = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private final MockitoHelper mMockitoHelper = new MockitoHelper(); protected MockWebServer mServer; protected MockContentResolverWithNotify mResolver; protected TestContext mTestContext; protected FakeSystemFacade mSystemFacade; protected static String STRING_1K; static { StringBuilder buff = new StringBuilder(); for (int i = 0; i < 1024; i++) { buff.append("a" + i % 26); } STRING_1K = buff.toString(); } static class MockContentResolverWithNotify extends MockContentResolver { public boolean mNotifyWasCalled = false; public MockContentResolverWithNotify(Context context) { super(context); } public synchronized void resetNotified() { mNotifyWasCalled = false; } @Override public synchronized void notifyChange( Uri uri, ContentObserver observer, boolean syncToNetwork) { mNotifyWasCalled = true; } } static class MockMediaProvider extends MockContentProvider { private static final Uri TEST_URI = Uri.parse("content://media/external/11111111"); @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public Uri insert(Uri uri, ContentValues values) { return TEST_URI; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return new MatrixCursor(new String[0], 0); } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 1; } @Override public Bundle call(String method, String request, Bundle args) { return new Bundle(); } @Override public IBinder getIContentProviderBinder() { return new Binder(); } } /** * Context passed to the provider and the service. Allows most methods to pass through to the * real Context (this is a LargeTest), with a few exceptions, including renaming file operations * to avoid file and DB conflicts (via RenamingDelegatingContext). */ static class TestContext extends RenamingDelegatingContext { private static final String FILENAME_PREFIX = "test."; private final ContentResolver mResolver; private final NotificationManager mNotifManager; private final DownloadManager mDownloadManager; private final JobScheduler mJobScheduler; public TestContext(Context realContext) { super(realContext, FILENAME_PREFIX); mResolver = new MockContentResolverWithNotify(this); mNotifManager = mock(NotificationManager.class); mDownloadManager = mock(DownloadManager.class); mJobScheduler = mock(JobScheduler.class); } /** * Direct DownloadService to our test instance of DownloadProvider. */ @Override public ContentResolver getContentResolver() { return mResolver; } /** * Stub some system services, allow access to others, and block the rest. */ @Override public Object getSystemService(String name) { if (Context.NOTIFICATION_SERVICE.equals(name)) { return mNotifManager; } else if (Context.DOWNLOAD_SERVICE.equals(name)) { return mDownloadManager; } else if (Context.JOB_SCHEDULER_SERVICE.equals(name)) { return mJobScheduler; } return super.getSystemService(name); } } public AbstractDownloadProviderFunctionalTest(FakeSystemFacade systemFacade) { super(DownloadJobService.class); mSystemFacade = systemFacade; } @Override protected void setUp() throws Exception { super.setUp(); mMockitoHelper.setUp(getClass()); // Since we're testing a system app, AppDataDirGuesser doesn't find our // cache dir, so set it explicitly. System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); final Context realContext = getContext(); mTestContext = new TestContext(realContext); mResolver = (MockContentResolverWithNotify) mTestContext.getContentResolver(); final DownloadProvider provider = new DownloadProvider(); provider.mSystemFacade = mSystemFacade; ProviderInfo info = new ProviderInfo(); info.authority = "downloads"; provider.attachInfo(mTestContext, info); mResolver.addProvider(PROVIDER_AUTHORITY, provider); mResolver.addProvider(MediaStore.AUTHORITY, new MockMediaProvider()); setContext(mTestContext); setupService(); Helpers.setSystemFacade(mSystemFacade); mSystemFacade.setUp(); assertDatabaseEmpty(); // ensure we're not messing with real data assertDatabaseSecureAgainstBadSelection(); mServer = new MockWebServer(); mServer.play(); } @Override protected void tearDown() throws Exception { cleanUpDownloads(); mServer.shutdown(); mMockitoHelper.tearDown(); super.tearDown(); } protected void startDownload(long id) { final JobParameters params = mock(JobParameters.class); when(params.getJobId()).thenReturn((int) id); getService().onBind(null); getService().onStartJob(params); } private void assertDatabaseEmpty() { try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, null, null, null)) { assertEquals(0, cursor.getCount()); } } private void assertDatabaseSecureAgainstBadSelection() { try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null, "('1'='1'))) ORDER BY lastmod DESC--", null, null)) { fail("Database isn't secure!"); } catch (Exception expected) { } } /** * Remove any downloaded files and delete any lingering downloads. */ void cleanUpDownloads() { if (mResolver == null) { return; } String[] columns = new String[] {Downloads.Impl._DATA}; Cursor cursor = mResolver.query(Downloads.Impl.CONTENT_URI, columns, null, null, null); try { for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { String filePath = cursor.getString(0); if (filePath == null) continue; Log.d(LOG_TAG, "Deleting " + filePath); new File(filePath).delete(); } } finally { cursor.close(); } mResolver.delete(Downloads.Impl.CONTENT_URI, null, null); } void enqueueResponse(MockResponse resp) { mServer.enqueue(resp); } MockResponse buildResponse(int status, String body) { return new MockResponse().setResponseCode(status).setBody(body) .setHeader("Content-type", "text/plain") .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); } MockResponse buildResponse(int status, byte[] body) { return new MockResponse().setResponseCode(status).setBody(body) .setHeader("Content-type", "text/plain") .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); } MockResponse buildEmptyResponse(int status) { return buildResponse(status, ""); } /** * Fetch the last request received by the MockWebServer. */ protected RecordedRequest takeRequest() throws InterruptedException { RecordedRequest request = mServer.takeRequest(); assertNotNull("Expected request was not made", request); return request; } String getServerUri(String path) throws MalformedURLException, UnknownHostException { return mServer.getUrl(path).toString(); } protected String readStream(InputStream inputStream) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); try { char[] buffer = new char[1024]; int length = reader.read(buffer); assertTrue("Failed to read anything from input stream", length > -1); return String.valueOf(buffer, 0, length); } finally { reader.close(); } } protected void assertStartsWith(String expectedPrefix, String actual) { String regex = "^" + expectedPrefix + ".*"; MoreAsserts.assertMatchesRegex(regex, actual); } }