blob: 71ce4dd0ffdaf0ac9b2faae432ae6e7bac468f5c [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 Sharkey7aa76012013-09-30 14:26:27 -070060 private static final boolean ROOTS_WEDGE = false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070061 private static final boolean ROOTS_CRASH = false;
62 private static final boolean ROOTS_REFRESH = false;
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070063
Jeff Sharkey7aa76012013-09-30 14:26:27 -070064 private static final boolean DOCUMENT_CRASH = false;
65
66 private static final boolean RECENT_WEDGE = false;
67
68 private static final boolean CHILD_WEDGE = false;
69 private static final boolean CHILD_CRASH = false;
70
Jeff Sharkeyd01571e2013-10-01 17:57:41 -070071 private static final boolean THUMB_HUNDREDS = false;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070072 private static final boolean THUMB_WEDGE = false;
73 private static final boolean THUMB_CRASH = false;
Jeff Sharkey954be022013-09-03 15:25:52 -070074
75 private static final String MY_ROOT_ID = "myRoot";
76 private static final String MY_DOC_ID = "myDoc";
77 private static final String MY_DOC_NULL = "myNull";
78
79 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Jeff Sharkey6efba222013-09-27 16:44:11 -070080 Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
Jeff Sharkey954be022013-09-03 15:25:52 -070081 Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
82 Root.COLUMN_AVAILABLE_BYTES,
83 };
84
85 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
86 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
87 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
88 };
89
90 private static String[] resolveRootProjection(String[] projection) {
91 return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
92 }
93
94 private static String[] resolveDocumentProjection(String[] projection) {
95 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
96 }
97
Jeff Sharkeya9ce0492013-09-19 15:25:56 -070098 private String mAuthority;
99
100 @Override
101 public void attachInfo(Context context, ProviderInfo info) {
102 mAuthority = info.authority;
103 super.attachInfo(context, info);
104 }
105
Jeff Sharkey954be022013-09-03 15:25:52 -0700106 @Override
107 public Cursor queryRoots(String[] projection) throws FileNotFoundException {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700108 Log.d(TAG, "Someone asked for our roots!");
109
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700110 if (LAG) lagUntilCanceled(null);
111 if (ROOTS_WEDGE) wedgeUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700112 if (ROOTS_CRASH) System.exit(12);
Jeff Sharkey954be022013-09-03 15:25:52 -0700113
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700114 if (ROOTS_REFRESH) {
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700115 new AsyncTask<Void, Void, Void>() {
116 @Override
117 protected Void doInBackground(Void... params) {
118 SystemClock.sleep(3000);
119 Log.d(TAG, "Notifying that something changed!!");
120 final Uri uri = DocumentsContract.buildRootsUri(mAuthority);
121 getContext().getContentResolver().notifyChange(uri, null, false);
122 return null;
123 }
124 }.execute();
125 }
126
Jeff Sharkey954be022013-09-03 15:25:52 -0700127 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
128 final RowBuilder row = result.newRow();
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700129 row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700130 row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
131 row.add(Root.COLUMN_TITLE, "_Test title which is really long");
Jeff Sharkeya9ce0492013-09-19 15:25:56 -0700132 row.add(Root.COLUMN_SUMMARY,
133 SystemClock.elapsedRealtime() + " summary which is also super long text");
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700134 row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
135 row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
Jeff Sharkey954be022013-09-03 15:25:52 -0700136 return result;
137 }
138
139 @Override
140 public Cursor queryDocument(String documentId, String[] projection)
141 throws FileNotFoundException {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700142 if (LAG) lagUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700143 if (DOCUMENT_CRASH) System.exit(12);
Jeff Sharkey954be022013-09-03 15:25:52 -0700144
145 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700146 includeFile(result, documentId, 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700147 return result;
148 }
149
150 /**
151 * Holds any outstanding or finished "network" fetching.
152 */
153 private WeakReference<CloudTask> mTask;
154
155 private static class CloudTask implements Runnable {
156
157 private final ContentResolver mResolver;
158 private final Uri mNotifyUri;
159
160 private volatile boolean mFinished;
161
162 public CloudTask(ContentResolver resolver, Uri notifyUri) {
163 mResolver = resolver;
164 mNotifyUri = notifyUri;
165 }
166
167 @Override
168 public void run() {
169 // Pretend to do some network
170 Log.d(TAG, hashCode() + ": pretending to do some network!");
171 SystemClock.sleep(2000);
172 Log.d(TAG, hashCode() + ": network done!");
173
174 mFinished = true;
175
176 // Tell anyone remotely they should requery
177 mResolver.notifyChange(mNotifyUri, null, false);
178 }
179
180 public boolean includeIfFinished(MatrixCursor result) {
181 Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished);
182 if (mFinished) {
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700183 includeFile(result, "_networkfile1", 0);
184 includeFile(result, "_networkfile2", 0);
185 includeFile(result, "_networkfile3", 0);
186 includeFile(result, "_networkfile4", 0);
187 includeFile(result, "_networkfile5", 0);
188 includeFile(result, "_networkfile6", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700189 return true;
190 } else {
191 return false;
192 }
193 }
194 }
195
196 private static class CloudCursor extends MatrixCursor {
197 public Object keepAlive;
198 public final Bundle extras = new Bundle();
199
200 public CloudCursor(String[] columnNames) {
201 super(columnNames);
202 }
203
204 @Override
205 public Bundle getExtras() {
206 return extras;
207 }
208 }
209
210 @Override
211 public Cursor queryChildDocuments(
212 String parentDocumentId, String[] projection, String sortOrder)
213 throws FileNotFoundException {
214
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700215 if (LAG) lagUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700216 if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
217 if (CHILD_CRASH) System.exit(12);
218
Jeff Sharkey954be022013-09-03 15:25:52 -0700219 final ContentResolver resolver = getContext().getContentResolver();
220 final Uri notifyUri = DocumentsContract.buildDocumentUri(
221 "com.example.documents", parentDocumentId);
222
223 CloudCursor result = new CloudCursor(resolveDocumentProjection(projection));
224 result.setNotificationUri(resolver, notifyUri);
225
226 // Always include local results
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700227 includeFile(result, MY_DOC_NULL, 0);
228 includeFile(result, "localfile1", 0);
229 includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL);
230 includeFile(result, "localfile3", 0);
231 includeFile(result, "localfile4", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700232
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700233 if (THUMB_HUNDREDS) {
234 for (int i = 0; i < 256; i++) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700235 includeFile(result, "i maded u an picshure" + i, Document.FLAG_SUPPORTS_THUMBNAIL);
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700236 }
237 }
238
Jeff Sharkey954be022013-09-03 15:25:52 -0700239 synchronized (this) {
240 // Try picking up an existing network fetch
241 CloudTask task = mTask != null ? mTask.get() : null;
242 if (task == null) {
243 Log.d(TAG, "No network task found; starting!");
244 task = new CloudTask(resolver, notifyUri);
245 mTask = new WeakReference<CloudTask>(task);
246 new Thread(task).start();
247
248 // Aggressively try freeing weak reference above
249 new Thread() {
250 @Override
251 public void run() {
252 while (mTask.get() != null) {
253 SystemClock.sleep(200);
254 System.gc();
255 System.runFinalization();
256 }
257 Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!");
258 }
259 }.start();
260 }
261
262 // Blend in cloud results if ready
263 if (task.includeIfFinished(result)) {
264 result.extras.putString(DocumentsContract.EXTRA_INFO,
265 "Everything Went Better Than Expected and this message is quite "
266 + "long and verbose and maybe even too long");
267 result.extras.putString(DocumentsContract.EXTRA_ERROR,
268 "But then again, maybe our server ran into an error, which means "
269 + "we're going to have a bad time");
270 } else {
271 result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
272 }
273
274 // Tie the network fetch to the cursor GC lifetime
275 result.keepAlive = task;
276
277 return result;
278 }
279 }
280
281 @Override
282 public Cursor queryRecentDocuments(String rootId, String[] projection)
283 throws FileNotFoundException {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700284
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700285 if (LAG) lagUntilCanceled(null);
286 if (RECENT_WEDGE) wedgeUntilCanceled(null);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700287
Jeff Sharkey954be022013-09-03 15:25:52 -0700288 // Pretend to take a super long time to respond
289 SystemClock.sleep(3000);
290
291 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700292 includeFile(
293 result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0);
Jeff Sharkey954be022013-09-03 15:25:52 -0700294 return result;
295 }
296
297 @Override
298 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
299 throws FileNotFoundException {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700300 if (LAG) lagUntilCanceled(null);
Jeff Sharkey954be022013-09-03 15:25:52 -0700301 throw new FileNotFoundException();
302 }
303
304 @Override
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700305 public AssetFileDescriptor openDocumentThumbnail(
306 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700307
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700308 if (LAG) lagUntilCanceled(signal);
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700309 if (THUMB_WEDGE) wedgeUntilCanceled(signal);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700310 if (THUMB_CRASH) System.exit(12);
311
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700312 final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
313 final Canvas canvas = new Canvas(bitmap);
314 final Paint paint = new Paint();
315 paint.setColor(Color.BLUE);
316 canvas.drawColor(Color.RED);
317 canvas.drawLine(0, 0, 32, 32, paint);
318
319 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
320 bitmap.compress(CompressFormat.JPEG, 50, bos);
321
322 final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
323 try {
324 final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe();
325 new AsyncTask<Object, Object, Object>() {
326 @Override
327 protected Object doInBackground(Object... params) {
328 final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor());
329 try {
330 Streams.copy(bis, fos);
331 } catch (IOException e) {
332 throw new RuntimeException(e);
333 }
334 IoUtils.closeQuietly(fds[1]);
335 return null;
336 }
337 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
338 return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH);
339 } catch (IOException e) {
340 throw new FileNotFoundException(e.getMessage());
341 }
342 }
343
344 @Override
Jeff Sharkey954be022013-09-03 15:25:52 -0700345 public boolean onCreate() {
346 return true;
347 }
348
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700349 private static void lagUntilCanceled(CancellationSignal signal) {
350 waitForCancelOrTimeout(signal, 1500);
351 }
352
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700353 private static void wedgeUntilCanceled(CancellationSignal signal) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700354 waitForCancelOrTimeout(signal, Integer.MAX_VALUE);
355 }
356
357 private static void waitForCancelOrTimeout(
358 final CancellationSignal signal, long timeoutMillis) {
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700359 if (signal != null) {
Jeff Sharkeyb3d42632013-10-22 17:09:44 -0700360 final Thread blocked = Thread.currentThread();
361 signal.setOnCancelListener(new OnCancelListener() {
362 @Override
363 public void onCancel() {
364 blocked.interrupt();
365 }
366 });
367 signal.throwIfCanceled();
368 }
369
370 try {
371 Thread.sleep(timeoutMillis);
372 } catch (InterruptedException e) {
373 }
374
375 if (signal != null) {
376 signal.throwIfCanceled();
Jeff Sharkeyd01571e2013-10-01 17:57:41 -0700377 }
378 }
379
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700380 private static void includeFile(MatrixCursor result, String docId, int flags) {
Jeff Sharkey954be022013-09-03 15:25:52 -0700381 final RowBuilder row = result.newRow();
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700382 row.add(Document.COLUMN_DOCUMENT_ID, docId);
383 row.add(Document.COLUMN_DISPLAY_NAME, docId);
384 row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
Jeff Sharkeyde2b22f2013-09-11 17:33:06 -0700385 row.add(Document.COLUMN_FLAGS, flags);
Jeff Sharkey954be022013-09-03 15:25:52 -0700386
387 if (MY_DOC_ID.equals(docId)) {
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700388 row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
Jeff Sharkey954be022013-09-03 15:25:52 -0700389 } else if (MY_DOC_NULL.equals(docId)) {
390 // No MIME type
391 } else {
Jeff Sharkeyb7757a62013-09-09 17:46:54 -0700392 row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream");
Jeff Sharkey954be022013-09-03 15:25:52 -0700393 }
394 }
395}