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
17package com.android.providers.downloads;
18
19import static org.mockito.Mockito.mock;
20import static org.mockito.Mockito.when;
21
22import android.app.DownloadManager;
23import android.app.NotificationManager;
24import android.app.job.JobParameters;
25import android.app.job.JobScheduler;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.pm.ProviderInfo;
29import android.database.ContentObserver;
30import android.database.Cursor;
31import android.net.Uri;
32import android.provider.Downloads;
33import android.test.MoreAsserts;
34import android.test.RenamingDelegatingContext;
35import android.test.ServiceTestCase;
36import android.test.mock.MockContentResolver;
37import android.util.Log;
38
39import com.google.mockwebserver.MockResponse;
40import com.google.mockwebserver.MockWebServer;
41import com.google.mockwebserver.RecordedRequest;
42import com.google.mockwebserver.SocketPolicy;
43
44import java.io.BufferedReader;
45import java.io.File;
46import java.io.IOException;
47import java.io.InputStream;
48import java.io.InputStreamReader;
49import java.net.MalformedURLException;
50import java.net.UnknownHostException;
51
52public 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
80        public MockContentResolverWithNotify(Context context) {
81            super(context);
82        }
83
84        public synchronized void resetNotified() {
85            mNotifyWasCalled = false;
86        }
87
88        @Override
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
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
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
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
141    public AbstractDownloadProviderFunctionalTest(FakeSystemFacade systemFacade) {
142        super(DownloadJobService.class);
143        mSystemFacade = systemFacade;
144    }
145
146    @Override
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        assertDatabaseEmpty(); // ensure we're not messing with real data
175        assertDatabaseSecureAgainstBadSelection();
176        mServer = new MockWebServer();
177        mServer.play();
178    }
179
180    @Override
181    protected void tearDown() throws Exception {
182        cleanUpDownloads();
183        mServer.shutdown();
184        mMockitoHelper.tearDown();
185        super.tearDown();
186    }
187
188    protected void startDownload(long id) {
189        final JobParameters params = mock(JobParameters.class);
190        when(params.getJobId()).thenReturn((int) id);
191        getService().onBind(null);
192        getService().onStartJob(params);
193    }
194
195    private void assertDatabaseEmpty() {
196        try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
197                null, null, null, null)) {
198            assertEquals(0, cursor.getCount());
199        }
200    }
201
202    private void assertDatabaseSecureAgainstBadSelection() {
203        try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null,
204                "('1'='1'))) ORDER BY lastmod DESC--", null, null)) {
205            fail("Database isn't secure!");
206        } catch (Exception expected) {
207        }
208    }
209
210    /**
211     * Remove any downloaded files and delete any lingering downloads.
212     */
213    void cleanUpDownloads() {
214        if (mResolver == null) {
215            return;
216        }
217        String[] columns = new String[] {Downloads.Impl._DATA};
218        Cursor cursor = mResolver.query(Downloads.Impl.CONTENT_URI, columns, null, null, null);
219        try {
220            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
221                String filePath = cursor.getString(0);
222                if (filePath == null) continue;
223                Log.d(LOG_TAG, "Deleting " + filePath);
224                new File(filePath).delete();
225            }
226        } finally {
227            cursor.close();
228        }
229        mResolver.delete(Downloads.Impl.CONTENT_URI, null, null);
230    }
231
232    void enqueueResponse(MockResponse resp) {
233        mServer.enqueue(resp);
234    }
235
236    MockResponse buildResponse(int status, String body) {
237        return new MockResponse().setResponseCode(status).setBody(body)
238                .setHeader("Content-type", "text/plain")
239                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
240    }
241
242    MockResponse buildResponse(int status, byte[] body) {
243        return new MockResponse().setResponseCode(status).setBody(body)
244                .setHeader("Content-type", "text/plain")
245                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
246    }
247
248    MockResponse buildEmptyResponse(int status) {
249        return buildResponse(status, "");
250    }
251
252    /**
253     * Fetch the last request received by the MockWebServer.
254     */
255    protected RecordedRequest takeRequest() throws InterruptedException {
256        RecordedRequest request = mServer.takeRequest();
257        assertNotNull("Expected request was not made", request);
258        return request;
259    }
260
261    String getServerUri(String path) throws MalformedURLException, UnknownHostException {
262        return mServer.getUrl(path).toString();
263    }
264
265    protected String readStream(InputStream inputStream) throws IOException {
266        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
267        try {
268            char[] buffer = new char[1024];
269            int length = reader.read(buffer);
270            assertTrue("Failed to read anything from input stream", length > -1);
271            return String.valueOf(buffer, 0, length);
272        } finally {
273            reader.close();
274        }
275    }
276
277    protected void assertStartsWith(String expectedPrefix, String actual) {
278        String regex = "^" + expectedPrefix + ".*";
279        MoreAsserts.assertMatchesRegex(regex, actual);
280    }
281}
282