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        assertTrue(isDatabaseEmpty()); // ensure we're not messing with real data
175        mServer = new MockWebServer();
176        mServer.play();
177    }
178
179    @Override
180    protected void tearDown() throws Exception {
181        cleanUpDownloads();
182        mServer.shutdown();
183        mMockitoHelper.tearDown();
184        super.tearDown();
185    }
186
187    protected void startDownload(long id) {
188        final JobParameters params = mock(JobParameters.class);
189        when(params.getJobId()).thenReturn((int) id);
190        getService().onStartJob(params);
191    }
192
193    private boolean isDatabaseEmpty() {
194        Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
195                null, null, null, null);
196        try {
197            return cursor.getCount() == 0;
198        } finally {
199            cursor.close();
200        }
201    }
202
203    /**
204     * Remove any downloaded files and delete any lingering downloads.
205     */
206    void cleanUpDownloads() {
207        if (mResolver == null) {
208            return;
209        }
210        String[] columns = new String[] {Downloads.Impl._DATA};
211        Cursor cursor = mResolver.query(Downloads.Impl.CONTENT_URI, columns, null, null, null);
212        try {
213            for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
214                String filePath = cursor.getString(0);
215                if (filePath == null) continue;
216                Log.d(LOG_TAG, "Deleting " + filePath);
217                new File(filePath).delete();
218            }
219        } finally {
220            cursor.close();
221        }
222        mResolver.delete(Downloads.Impl.CONTENT_URI, null, null);
223    }
224
225    void enqueueResponse(MockResponse resp) {
226        mServer.enqueue(resp);
227    }
228
229    MockResponse buildResponse(int status, String body) {
230        return new MockResponse().setResponseCode(status).setBody(body)
231                .setHeader("Content-type", "text/plain")
232                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
233    }
234
235    MockResponse buildResponse(int status, byte[] body) {
236        return new MockResponse().setResponseCode(status).setBody(body)
237                .setHeader("Content-type", "text/plain")
238                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
239    }
240
241    MockResponse buildEmptyResponse(int status) {
242        return buildResponse(status, "");
243    }
244
245    /**
246     * Fetch the last request received by the MockWebServer.
247     */
248    protected RecordedRequest takeRequest() throws InterruptedException {
249        RecordedRequest request = mServer.takeRequest();
250        assertNotNull("Expected request was not made", request);
251        return request;
252    }
253
254    String getServerUri(String path) throws MalformedURLException, UnknownHostException {
255        return mServer.getUrl(path).toString();
256    }
257
258    protected String readStream(InputStream inputStream) throws IOException {
259        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
260        try {
261            char[] buffer = new char[1024];
262            int length = reader.read(buffer);
263            assertTrue("Failed to read anything from input stream", length > -1);
264            return String.valueOf(buffer, 0, length);
265        } finally {
266            reader.close();
267        }
268    }
269
270    protected void assertStartsWith(String expectedPrefix, String actual) {
271        String regex = "^" + expectedPrefix + ".*";
272        MoreAsserts.assertMatchesRegex(regex, actual);
273    }
274}
275