blob: 8eb70e96b2d4e42a521a670beba8e851cdfed363 [file] [log] [blame]
Jeff Sharkey954be022013-09-03 15:25:52 -07001/*
2 * Copyright (C) 2013 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.externalstorage;
18
19import android.content.ContentResolver;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070020import android.content.Context;
21import android.content.pm.ProviderInfo;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070022import android.content.res.AssetFileDescriptor;
Jeff Sharkey954be022013-09-03 15:25:52 -070023import android.database.Cursor;
24import android.database.MatrixCursor;
25import android.database.MatrixCursor.RowBuilder;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070026import android.graphics.Bitmap;
27import android.graphics.Bitmap.CompressFormat;
28import android.graphics.Canvas;
29import android.graphics.Color;
30import android.graphics.Paint;
31import android.graphics.Point;
Jeff Sharkey954be022013-09-03 15:25:52 -070032import android.net.Uri;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070033import android.os.AsyncTask;
Jeff Sharkey954be022013-09-03 15:25:52 -070034import android.os.Bundle;
35import android.os.CancellationSignal;
Jeff Sharkeyb3d42632013-10-22 17:09:44 -070036import android.os.CancellationSignal.OnCancelListener;
Jeff Sharkey954be022013-09-03 15:25:52 -070037import android.os.ParcelFileDescriptor;
38import android.os.SystemClock;
39import android.provider.DocumentsContract;
40import android.provider.DocumentsContract.Document;
41import android.provider.DocumentsContract.Root;
42import android.provider.DocumentsProvider;
43import android.util.Log;
44
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070045import libcore.io.IoUtils;
46import libcore.io.Streams;
47
48import java.io.ByteArrayInputStream;
49import java.io.ByteArrayOutputStream;
Jeff Sharkey954be022013-09-03 15:25:52 -070050import java.io.FileNotFoundException;
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -070051import java.io.FileOutputStream;
52import java.io.IOException;
Jeff Sharkey954be022013-09-03 15:25:52 -070053import java.lang.ref.WeakReference;
54
55public class TestDocumentsProvider extends DocumentsProvider {
56 private static final String TAG = "TestDocuments";
57
Jeff Sharkeyb3d42632013-10-22 17:09:44 -070058 private static final boolean LAG = false;
59
Jeff Sharkey5d321d42013-10-29 11:48:26 -070060 private static final boolean ROOT_LAME_PROJECTION = false;
61 private static final boolean DOCUMENT_LAME_PROJECTION = false;
62
Jeff Sharkey7aa76012013-09-30 14:26:27 -070063 private static final boolean ROOTS_WEDGE = false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070064 private static final boolean ROOTS_CRASH = false;
65 private static final boolean ROOTS_REFRESH = false;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070066
Jeff Sharkey7aa76012013-09-30 14:26:27 -070067 private static final boolean DOCUMENT_CRASH = false;
68
69 private static final boolean RECENT_WEDGE = false;
70
71 private static final boolean CHILD_WEDGE = false;
72 private static final boolean CHILD_CRASH = false;
73
Jeff Sharkeyd01571e2013-10-01 17:57:41 -070074 private static final boolean THUMB_HUNDREDS = false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070075 private static final boolean THUMB_WEDGE = false;
76 private static final boolean THUMB_CRASH = false;
Jeff Sharkey954be022013-09-03 15:25:52 -070077
78 private static final String MY_ROOT_ID = "myRoot";
79 private static final String MY_DOC_ID = "myDoc";
80 private static final String MY_DOC_NULL = "myNull";
81
82 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Jeff Sharkey6efba222013-09-27 16:44:11 -070083 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
Jeff Sharkey954be022013-09-03 15:25:52 -070084 Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
85 Root.COLUMN_AVAILABLE_BYTES,
86 };
87
88 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
89 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
90 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
91 };
92
93 private static String[] resolveRootProjection(String[] projection) {
Jeff Sharkey5d321d42013-10-29 11:48:26 -070094 if (ROOT_LAME_PROJECTION) return new String[0];
Jeff Sharkey954be022013-09-03 15:25:52 -070095 return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
96 }
97
98 private static String[] resolveDocumentProjection(String[] projection) {
Jeff Sharkey5d321d42013-10-29 11:48:26 -070099 if (DOCUMENT_LAME_PROJECTION) return new String[0];
Jeff Sharkey954be022013-09-03 15:25:52 -0700100 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
101 }
102
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700103 private String mAuthority;
104
105 @Override
106 public void attachInfo(Context context, ProviderInfo info) {
107 mAuthority = info.authority;
108 super.attachInfo(context, info);
109 }
110
Jeff Sharkey954be022013-09-03 15:25:52 -0700111 @Override
112 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700113 Log.d(TAG, "Someone asked for our roots!");
114
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700115 if (LAG) lagUntilCanceled(null);
116 if (ROOTS_WEDGE) wedgeUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700117 if (ROOTS_CRASH) System.exit(12);
Jeff Sharkey954be022013-09-03 15:25:52 -0700118
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700119 if (ROOTS_REFRESH) {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700120 new AsyncTask<Void, Void, Void>() {
121 @Override
122 protected Void doInBackground(Void... params) {
123 SystemClock.sleep(3000);
124 Log.d(TAG, "Notifying that something changed!!");
125 final Uri uri = DocumentsContract.buildRootsUri(mAuthority);
126 getContext().getContentResolver().notifyChange(uri, null, false);
127 return null;
128 }
129 }.execute();
130 }
131
Jeff Sharkey954be022013-09-03 15:25:52 -0700132 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
133 final RowBuilder row = result.newRow();
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700134 row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
Jeff Sharkey04d45a02013-10-23 15:46:38 -0700135 row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_CREATE);
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700136 row.add(Root.COLUMN_TITLE, "_Test title which is really long");
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700137 row.add(Root.COLUMN_SUMMARY,
138 SystemClock.elapsedRealtime() + " summary which is also super long text");
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700139 row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
140 row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
Jeff Sharkey954be022013-09-03 15:25:52 -0700141 return result;
142 }
143
144 @Override
145 public Cursor queryDocument(String documentId, String[] projection)
146 throws FileNotFoundException {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700147 if (LAG) lagUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700148 if (DOCUMENT_CRASH) System.exit(12);
Jeff Sharkey954be022013-09-03 15:25:52 -0700149
150 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700151 includeFile(result, documentId, 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700152 return result;
153 }
154
Jeff Sharkey04d45a02013-10-23 15:46:38 -0700155 @Override
156 public String createDocument(String parentDocumentId, String mimeType, String displayName)
157 throws FileNotFoundException {
158 if (LAG) lagUntilCanceled(null);
159
160 return super.createDocument(parentDocumentId, mimeType, displayName);
161 }
162
Jeff Sharkey954be022013-09-03 15:25:52 -0700163 /**
164 * Holds any outstanding or finished "network" fetching.
165 */
166 private WeakReference<CloudTask> mTask;
167
168 private static class CloudTask implements Runnable {
169
170 private final ContentResolver mResolver;
171 private final Uri mNotifyUri;
172
173 private volatile boolean mFinished;
174
175 public CloudTask(ContentResolver resolver, Uri notifyUri) {
176 mResolver = resolver;
177 mNotifyUri = notifyUri;
178 }
179
180 @Override
181 public void run() {
182 // Pretend to do some network
183 Log.d(TAG, hashCode() + ": pretending to do some network!");
184 SystemClock.sleep(2000);
185 Log.d(TAG, hashCode() + ": network done!");
186
187 mFinished = true;
188
189 // Tell anyone remotely they should requery
190 mResolver.notifyChange(mNotifyUri, null, false);
191 }
192
193 public boolean includeIfFinished(MatrixCursor result) {
194 Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished);
195 if (mFinished) {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700196 includeFile(result, "_networkfile1", 0);
197 includeFile(result, "_networkfile2", 0);
198 includeFile(result, "_networkfile3", 0);
199 includeFile(result, "_networkfile4", 0);
200 includeFile(result, "_networkfile5", 0);
201 includeFile(result, "_networkfile6", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700202 return true;
203 } else {
204 return false;
205 }
206 }
207 }
208
209 private static class CloudCursor extends MatrixCursor {
210 public Object keepAlive;
211 public final Bundle extras = new Bundle();
212
213 public CloudCursor(String[] columnNames) {
214 super(columnNames);
215 }
216
217 @Override
218 public Bundle getExtras() {
219 return extras;
220 }
221 }
222
223 @Override
224 public Cursor queryChildDocuments(
225 String parentDocumentId, String[] projection, String sortOrder)
226 throws FileNotFoundException {
227
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700228 if (LAG) lagUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700229 if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
230 if (CHILD_CRASH) System.exit(12);
231
Jeff Sharkey954be022013-09-03 15:25:52 -0700232 final ContentResolver resolver = getContext().getContentResolver();
233 final Uri notifyUri = DocumentsContract.buildDocumentUri(
234 "com.example.documents", parentDocumentId);
235
236 CloudCursor result = new CloudCursor(resolveDocumentProjection(projection));
237 result.setNotificationUri(resolver, notifyUri);
238
239 // Always include local results
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700240 includeFile(result, MY_DOC_NULL, 0);
241 includeFile(result, "localfile1", 0);
242 includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL);
243 includeFile(result, "localfile3", 0);
244 includeFile(result, "localfile4", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700245
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700246 if (THUMB_HUNDREDS) {
247 for (int i = 0; i < 256; i++) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700248 includeFile(result, "i maded u an picshure" + i, Document.FLAG_SUPPORTS_THUMBNAIL);
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700249 }
250 }
251
Jeff Sharkey954be022013-09-03 15:25:52 -0700252 synchronized (this) {
253 // Try picking up an existing network fetch
254 CloudTask task = mTask != null ? mTask.get() : null;
255 if (task == null) {
256 Log.d(TAG, "No network task found; starting!");
257 task = new CloudTask(resolver, notifyUri);
258 mTask = new WeakReference<CloudTask>(task);
259 new Thread(task).start();
260
261 // Aggressively try freeing weak reference above
262 new Thread() {
263 @Override
264 public void run() {
265 while (mTask.get() != null) {
266 SystemClock.sleep(200);
267 System.gc();
268 System.runFinalization();
269 }
270 Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!");
271 }
272 }.start();
273 }
274
275 // Blend in cloud results if ready
276 if (task.includeIfFinished(result)) {
277 result.extras.putString(DocumentsContract.EXTRA_INFO,
278 "Everything Went Better Than Expected and this message is quite "
279 + "long and verbose and maybe even too long");
280 result.extras.putString(DocumentsContract.EXTRA_ERROR,
281 "But then again, maybe our server ran into an error, which means "
282 + "we're going to have a bad time");
283 } else {
284 result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
285 }
286
287 // Tie the network fetch to the cursor GC lifetime
288 result.keepAlive = task;
289
290 return result;
291 }
292 }
293
294 @Override
295 public Cursor queryRecentDocuments(String rootId, String[] projection)
296 throws FileNotFoundException {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700297
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700298 if (LAG) lagUntilCanceled(null);
299 if (RECENT_WEDGE) wedgeUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700300
Jeff Sharkey954be022013-09-03 15:25:52 -0700301 // Pretend to take a super long time to respond
302 SystemClock.sleep(3000);
303
304 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700305 includeFile(
306 result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700307 return result;
308 }
309
310 @Override
311 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
312 throws FileNotFoundException {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700313 if (LAG) lagUntilCanceled(null);
Jeff Sharkey954be022013-09-03 15:25:52 -0700314 throw new FileNotFoundException();
315 }
316
317 @Override
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700318 public AssetFileDescriptor openDocumentThumbnail(
319 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700320
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700321 if (LAG) lagUntilCanceled(signal);
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700322 if (THUMB_WEDGE) wedgeUntilCanceled(signal);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700323 if (THUMB_CRASH) System.exit(12);
324
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700325 final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
326 final Canvas canvas = new Canvas(bitmap);
327 final Paint paint = new Paint();
328 paint.setColor(Color.BLUE);
329 canvas.drawColor(Color.RED);
330 canvas.drawLine(0, 0, 32, 32, paint);
331
332 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
333 bitmap.compress(CompressFormat.JPEG, 50, bos);
334
335 final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
336 try {
337 final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe();
338 new AsyncTask<Object, Object, Object>() {
339 @Override
340 protected Object doInBackground(Object... params) {
341 final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor());
342 try {
343 Streams.copy(bis, fos);
344 } catch (IOException e) {
345 throw new RuntimeException(e);
346 }
347 IoUtils.closeQuietly(fds[1]);
348 return null;
349 }
350 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
351 return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH);
352 } catch (IOException e) {
353 throw new FileNotFoundException(e.getMessage());
354 }
355 }
356
357 @Override
Jeff Sharkey954be022013-09-03 15:25:52 -0700358 public boolean onCreate() {
359 return true;
360 }
361
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700362 private static void lagUntilCanceled(CancellationSignal signal) {
363 waitForCancelOrTimeout(signal, 1500);
364 }
365
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700366 private static void wedgeUntilCanceled(CancellationSignal signal) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700367 waitForCancelOrTimeout(signal, Integer.MAX_VALUE);
368 }
369
370 private static void waitForCancelOrTimeout(
371 final CancellationSignal signal, long timeoutMillis) {
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700372 if (signal != null) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700373 final Thread blocked = Thread.currentThread();
374 signal.setOnCancelListener(new OnCancelListener() {
375 @Override
376 public void onCancel() {
377 blocked.interrupt();
378 }
379 });
380 signal.throwIfCanceled();
381 }
382
383 try {
384 Thread.sleep(timeoutMillis);
385 } catch (InterruptedException e) {
386 }
387
388 if (signal != null) {
389 signal.throwIfCanceled();
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700390 }
391 }
392
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700393 private static void includeFile(MatrixCursor result, String docId, int flags) {
Jeff Sharkey954be022013-09-03 15:25:52 -0700394 final RowBuilder row = result.newRow();
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700395 row.add(Document.COLUMN_DOCUMENT_ID, docId);
396 row.add(Document.COLUMN_DISPLAY_NAME, docId);
397 row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700398 row.add(Document.COLUMN_FLAGS, flags);
Jeff Sharkey954be022013-09-03 15:25:52 -0700399
400 if (MY_DOC_ID.equals(docId)) {
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700401 row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
Jeff Sharkey04d45a02013-10-23 15:46:38 -0700402 row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_SUPPORTS_CREATE);
Jeff Sharkey954be022013-09-03 15:25:52 -0700403 } else if (MY_DOC_NULL.equals(docId)) {
404 // No MIME type
405 } else {
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700406 row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream");
Jeff Sharkey954be022013-09-03 15:25:52 -0700407 }
408 }
409}