OSDN Git Service

71ce4dd0ffdaf0ac9b2faae432ae6e7bac468f5c
[android-x86/frameworks-base.git] / packages / ExternalStorageProvider / src / com / android / externalstorage / TestDocumentsProvider.java
1 /*
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
17 package com.android.externalstorage;
18
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.pm.ProviderInfo;
22 import android.content.res.AssetFileDescriptor;
23 import android.database.Cursor;
24 import android.database.MatrixCursor;
25 import android.database.MatrixCursor.RowBuilder;
26 import android.graphics.Bitmap;
27 import android.graphics.Bitmap.CompressFormat;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.Paint;
31 import android.graphics.Point;
32 import android.net.Uri;
33 import android.os.AsyncTask;
34 import android.os.Bundle;
35 import android.os.CancellationSignal;
36 import android.os.CancellationSignal.OnCancelListener;
37 import android.os.ParcelFileDescriptor;
38 import android.os.SystemClock;
39 import android.provider.DocumentsContract;
40 import android.provider.DocumentsContract.Document;
41 import android.provider.DocumentsContract.Root;
42 import android.provider.DocumentsProvider;
43 import android.util.Log;
44
45 import libcore.io.IoUtils;
46 import libcore.io.Streams;
47
48 import java.io.ByteArrayInputStream;
49 import java.io.ByteArrayOutputStream;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.lang.ref.WeakReference;
54
55 public class TestDocumentsProvider extends DocumentsProvider {
56     private static final String TAG = "TestDocuments";
57
58     private static final boolean LAG = false;
59
60     private static final boolean ROOTS_WEDGE = false;
61     private static final boolean ROOTS_CRASH = false;
62     private static final boolean ROOTS_REFRESH = false;
63
64     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
71     private static final boolean THUMB_HUNDREDS = false;
72     private static final boolean THUMB_WEDGE = false;
73     private static final boolean THUMB_CRASH = false;
74
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[] {
80             Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
81             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
98     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
106     @Override
107     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
108         Log.d(TAG, "Someone asked for our roots!");
109
110         if (LAG) lagUntilCanceled(null);
111         if (ROOTS_WEDGE) wedgeUntilCanceled(null);
112         if (ROOTS_CRASH) System.exit(12);
113
114         if (ROOTS_REFRESH) {
115             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
127         final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
128         final RowBuilder row = result.newRow();
129         row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
130         row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
131         row.add(Root.COLUMN_TITLE, "_Test title which is really long");
132         row.add(Root.COLUMN_SUMMARY,
133                 SystemClock.elapsedRealtime() + " summary which is also super long text");
134         row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
135         row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
136         return result;
137     }
138
139     @Override
140     public Cursor queryDocument(String documentId, String[] projection)
141             throws FileNotFoundException {
142         if (LAG) lagUntilCanceled(null);
143         if (DOCUMENT_CRASH) System.exit(12);
144
145         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
146         includeFile(result, documentId, 0);
147         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) {
183                 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);
189                 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
215         if (LAG) lagUntilCanceled(null);
216         if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
217         if (CHILD_CRASH) System.exit(12);
218
219         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
227         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);
232
233         if (THUMB_HUNDREDS) {
234             for (int i = 0; i < 256; i++) {
235                 includeFile(result, "i maded u an picshure" + i, Document.FLAG_SUPPORTS_THUMBNAIL);
236             }
237         }
238
239         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 {
284
285         if (LAG) lagUntilCanceled(null);
286         if (RECENT_WEDGE) wedgeUntilCanceled(null);
287
288         // Pretend to take a super long time to respond
289         SystemClock.sleep(3000);
290
291         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
292         includeFile(
293                 result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0);
294         return result;
295     }
296
297     @Override
298     public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
299             throws FileNotFoundException {
300         if (LAG) lagUntilCanceled(null);
301         throw new FileNotFoundException();
302     }
303
304     @Override
305     public AssetFileDescriptor openDocumentThumbnail(
306             String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
307
308         if (LAG) lagUntilCanceled(signal);
309         if (THUMB_WEDGE) wedgeUntilCanceled(signal);
310         if (THUMB_CRASH) System.exit(12);
311
312         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
345     public boolean onCreate() {
346         return true;
347     }
348
349     private static void lagUntilCanceled(CancellationSignal signal) {
350         waitForCancelOrTimeout(signal, 1500);
351     }
352
353     private static void wedgeUntilCanceled(CancellationSignal signal) {
354         waitForCancelOrTimeout(signal, Integer.MAX_VALUE);
355     }
356
357     private static void waitForCancelOrTimeout(
358             final CancellationSignal signal, long timeoutMillis) {
359         if (signal != null) {
360             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();
377         }
378     }
379
380     private static void includeFile(MatrixCursor result, String docId, int flags) {
381         final RowBuilder row = result.newRow();
382         row.add(Document.COLUMN_DOCUMENT_ID, docId);
383         row.add(Document.COLUMN_DISPLAY_NAME, docId);
384         row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
385         row.add(Document.COLUMN_FLAGS, flags);
386
387         if (MY_DOC_ID.equals(docId)) {
388             row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
389         } else if (MY_DOC_NULL.equals(docId)) {
390             // No MIME type
391         } else {
392             row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream");
393         }
394     }
395 }