1954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey/*
2954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * Copyright (C) 2013 The Android Open Source Project
3954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey *
4954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * you may not use this file except in compliance with the License.
6954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * You may obtain a copy of the License at
7954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey *
8954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey *
10954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
11954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * See the License for the specific language governing permissions and
14954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey * limitations under the License.
15954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey */
16954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
17954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeypackage com.android.externalstorage;
18954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
19954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.content.ContentResolver;
20a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkeyimport android.content.Context;
21a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkeyimport android.content.pm.ProviderInfo;
22de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.content.res.AssetFileDescriptor;
23954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.database.Cursor;
24954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.database.MatrixCursor;
25954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.database.MatrixCursor.RowBuilder;
26de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Bitmap;
27de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Bitmap.CompressFormat;
28de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Canvas;
29de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Color;
30de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Paint;
31de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.graphics.Point;
32954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.net.Uri;
33de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport android.os.AsyncTask;
34954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.os.Bundle;
35954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.os.CancellationSignal;
36b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkeyimport android.os.CancellationSignal.OnCancelListener;
37954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.os.ParcelFileDescriptor;
38954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.os.SystemClock;
39954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.provider.DocumentsContract;
40954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.provider.DocumentsContract.Document;
41954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.provider.DocumentsContract.Root;
42954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.provider.DocumentsProvider;
43954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport android.util.Log;
44954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
45de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport libcore.io.IoUtils;
46de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport libcore.io.Streams;
47de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey
48de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport java.io.ByteArrayInputStream;
49de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport java.io.ByteArrayOutputStream;
50954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport java.io.FileNotFoundException;
51de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport java.io.FileOutputStream;
52de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkeyimport java.io.IOException;
53954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeyimport java.lang.ref.WeakReference;
54954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
55954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkeypublic class TestDocumentsProvider extends DocumentsProvider {
56954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String TAG = "TestDocuments";
57954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
58b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey    private static final boolean LAG = false;
59b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey
605d321d472d9983db52610393e6506e2b2d2da4bfJeff Sharkey    private static final boolean ROOT_LAME_PROJECTION = false;
615d321d472d9983db52610393e6506e2b2d2da4bfJeff Sharkey    private static final boolean DOCUMENT_LAME_PROJECTION = false;
625d321d472d9983db52610393e6506e2b2d2da4bfJeff Sharkey
637aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean ROOTS_WEDGE = false;
647aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean ROOTS_CRASH = false;
657aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean ROOTS_REFRESH = false;
66a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey
677aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean DOCUMENT_CRASH = false;
687aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
697aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean RECENT_WEDGE = false;
707aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
717aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean CHILD_WEDGE = false;
727aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean CHILD_CRASH = false;
737aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
74d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey    private static final boolean THUMB_HUNDREDS = false;
757aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean THUMB_WEDGE = false;
767aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey    private static final boolean THUMB_CRASH = false;
77954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
78954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String MY_ROOT_ID = "myRoot";
79954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String MY_DOC_ID = "myDoc";
80954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String MY_DOC_NULL = "myNull";
81954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
82954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
836efba22ce510352bb84910d6efc42fecafd31ed7Jeff Sharkey            Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
84954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
85954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Root.COLUMN_AVAILABLE_BYTES,
86954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    };
87954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
88954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
89954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME,
90954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,
91954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    };
92954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
93954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static String[] resolveRootProjection(String[] projection) {
945d321d472d9983db52610393e6506e2b2d2da4bfJeff Sharkey        if (ROOT_LAME_PROJECTION) return new String[0];
95954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
96954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
97954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
98954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static String[] resolveDocumentProjection(String[] projection) {
995d321d472d9983db52610393e6506e2b2d2da4bfJeff Sharkey        if (DOCUMENT_LAME_PROJECTION) return new String[0];
100954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
101954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
102954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
103a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey    private String mAuthority;
104a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey
105a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey    @Override
106a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey    public void attachInfo(Context context, ProviderInfo info) {
107a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey        mAuthority = info.authority;
108a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey        super.attachInfo(context, info);
109a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey    }
110a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey
111954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
112954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
113a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey        Log.d(TAG, "Someone asked for our roots!");
114a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey
115b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(null);
116b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (ROOTS_WEDGE) wedgeUntilCanceled(null);
1177aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (ROOTS_CRASH) System.exit(12);
118954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
1197aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (ROOTS_REFRESH) {
120a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey            new AsyncTask<Void, Void, Void>() {
121a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                @Override
122a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                protected Void doInBackground(Void... params) {
123a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                    SystemClock.sleep(3000);
124a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                    Log.d(TAG, "Notifying that something changed!!");
125a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                    final Uri uri = DocumentsContract.buildRootsUri(mAuthority);
126a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                    getContext().getContentResolver().notifyChange(uri, null, false);
127a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                    return null;
128a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                }
129a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey            }.execute();
130a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey        }
131a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey
132954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
133954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final RowBuilder row = result.newRow();
134b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
13504d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey        row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_CREATE);
136b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Root.COLUMN_TITLE, "_Test title which is really long");
137a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey        row.add(Root.COLUMN_SUMMARY,
138a9ce049db87259e302e2368d2a4a1c11a94fd831Jeff Sharkey                SystemClock.elapsedRealtime() + " summary which is also super long text");
139b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
140b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
141954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return result;
142954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
143954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
144954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
145954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public Cursor queryDocument(String documentId, String[] projection)
146954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            throws FileNotFoundException {
147b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(null);
1487aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (DOCUMENT_CRASH) System.exit(12);
149954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
150954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
151de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, documentId, 0);
152954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return result;
153954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
154954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
15504d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey    @Override
15604d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey    public String createDocument(String parentDocumentId, String mimeType, String displayName)
15704d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey            throws FileNotFoundException {
15804d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey        if (LAG) lagUntilCanceled(null);
15904d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey
16004d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey        return super.createDocument(parentDocumentId, mimeType, displayName);
16104d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey    }
16204d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey
163954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    /**
164954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey     * Holds any outstanding or finished "network" fetching.
165954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey     */
166954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private WeakReference<CloudTask> mTask;
167954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
168954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static class CloudTask implements Runnable {
169954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
170954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        private final ContentResolver mResolver;
171954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        private final Uri mNotifyUri;
172954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
173954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        private volatile boolean mFinished;
174954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
175954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public CloudTask(ContentResolver resolver, Uri notifyUri) {
176954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            mResolver = resolver;
177954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            mNotifyUri = notifyUri;
178954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
179954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
180954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        @Override
181954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public void run() {
182954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // Pretend to do some network
183954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Log.d(TAG, hashCode() + ": pretending to do some network!");
184954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            SystemClock.sleep(2000);
185954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Log.d(TAG, hashCode() + ": network done!");
186954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
187954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            mFinished = true;
188954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
189954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // Tell anyone remotely they should requery
190954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            mResolver.notifyChange(mNotifyUri, null, false);
191954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
192954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
193954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public boolean includeIfFinished(MatrixCursor result) {
194954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished);
195954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            if (mFinished) {
196de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile1", 0);
197de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile2", 0);
198de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile3", 0);
199de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile4", 0);
200de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile5", 0);
201de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                includeFile(result, "_networkfile6", 0);
202954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                return true;
203954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            } else {
204954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                return false;
205954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            }
206954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
207954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
208954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
209954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    private static class CloudCursor extends MatrixCursor {
210954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public Object keepAlive;
211954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public final Bundle extras = new Bundle();
212954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
213954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public CloudCursor(String[] columnNames) {
214954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            super(columnNames);
215954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
216954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
217954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        @Override
218954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        public Bundle getExtras() {
219954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            return extras;
220954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
221954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
222954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
223954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
224954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public Cursor queryChildDocuments(
225954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            String parentDocumentId, String[] projection, String sortOrder)
226954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            throws FileNotFoundException {
227954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
228b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(null);
2297aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
2307aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (CHILD_CRASH) System.exit(12);
2317aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
232954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final ContentResolver resolver = getContext().getContentResolver();
233954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final Uri notifyUri = DocumentsContract.buildDocumentUri(
234954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                "com.example.documents", parentDocumentId);
235954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
236954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        CloudCursor result = new CloudCursor(resolveDocumentProjection(projection));
237954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        result.setNotificationUri(resolver, notifyUri);
238954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
239954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        // Always include local results
240de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, MY_DOC_NULL, 0);
241de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, "localfile1", 0);
242de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL);
243de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, "localfile3", 0);
244de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(result, "localfile4", 0);
245954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
246d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey        if (THUMB_HUNDREDS) {
247d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey            for (int i = 0; i < 256; i++) {
248b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey                includeFile(result, "i maded u an picshure" + i, Document.FLAG_SUPPORTS_THUMBNAIL);
249d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey            }
250d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey        }
251d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey
252954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        synchronized (this) {
253954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // Try picking up an existing network fetch
254954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            CloudTask task = mTask != null ? mTask.get() : null;
255954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            if (task == null) {
256954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                Log.d(TAG, "No network task found; starting!");
257954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                task = new CloudTask(resolver, notifyUri);
258954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                mTask = new WeakReference<CloudTask>(task);
259954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                new Thread(task).start();
260954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
261954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                // Aggressively try freeing weak reference above
262954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                new Thread() {
263954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                    @Override
264954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                    public void run() {
265954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                        while (mTask.get() != null) {
266954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                            SystemClock.sleep(200);
267954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                            System.gc();
268954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                            System.runFinalization();
269954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                        }
270954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                        Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!");
271954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                    }
272954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                }.start();
273954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            }
274954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
275954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // Blend in cloud results if ready
276954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            if (task.includeIfFinished(result)) {
277954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                result.extras.putString(DocumentsContract.EXTRA_INFO,
278954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                        "Everything Went Better Than Expected and this message is quite "
279954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                                + "long and verbose and maybe even too long");
280954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                result.extras.putString(DocumentsContract.EXTRA_ERROR,
281954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                        "But then again, maybe our server ran into an error, which means "
282954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                                + "we're going to have a bad time");
283954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            } else {
284954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey                result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
285954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            }
286954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
287954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // Tie the network fetch to the cursor GC lifetime
288954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            result.keepAlive = task;
289954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
290954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            return result;
291954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
292954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
293954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
294954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
295954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public Cursor queryRecentDocuments(String rootId, String[] projection)
296954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            throws FileNotFoundException {
2977aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
298b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(null);
299b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (RECENT_WEDGE) wedgeUntilCanceled(null);
3007aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
301954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        // Pretend to take a super long time to respond
302954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        SystemClock.sleep(3000);
303954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
304954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
305de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        includeFile(
306de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0);
307954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return result;
308954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
309954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
310954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
311954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
312954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            throws FileNotFoundException {
313b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(null);
314954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        throw new FileNotFoundException();
315954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
316954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
317954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    @Override
318de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey    public AssetFileDescriptor openDocumentThumbnail(
319de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
3207aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
321b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (LAG) lagUntilCanceled(signal);
322d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey        if (THUMB_WEDGE) wedgeUntilCanceled(signal);
3237aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey        if (THUMB_CRASH) System.exit(12);
3247aa7601c09ab5d87cc15a0ed9a8f511d494a4cbcJeff Sharkey
325de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
326de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        final Canvas canvas = new Canvas(bitmap);
327de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        final Paint paint = new Paint();
328de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        paint.setColor(Color.BLUE);
329de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        canvas.drawColor(Color.RED);
330de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        canvas.drawLine(0, 0, 32, 32, paint);
331de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey
332de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
333de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        bitmap.compress(CompressFormat.JPEG, 50, bos);
334de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey
335de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
336de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        try {
337de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe();
338de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            new AsyncTask<Object, Object, Object>() {
339de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                @Override
340de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                protected Object doInBackground(Object... params) {
341de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor());
342de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    try {
343de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                        Streams.copy(bis, fos);
344de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    } catch (IOException e) {
345de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                        throw new RuntimeException(e);
346de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    }
347de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    IoUtils.closeQuietly(fds[1]);
348de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                    return null;
349de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey                }
350de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
351de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH);
352de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        } catch (IOException e) {
353de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey            throw new FileNotFoundException(e.getMessage());
354de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        }
355de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey    }
356de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey
357de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey    @Override
358954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    public boolean onCreate() {
359954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        return true;
360954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
361954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
362b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey    private static void lagUntilCanceled(CancellationSignal signal) {
363b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        waitForCancelOrTimeout(signal, 1500);
364b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey    }
365b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey
366d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey    private static void wedgeUntilCanceled(CancellationSignal signal) {
367b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        waitForCancelOrTimeout(signal, Integer.MAX_VALUE);
368b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey    }
369b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey
370b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey    private static void waitForCancelOrTimeout(
371b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            final CancellationSignal signal, long timeoutMillis) {
372d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey        if (signal != null) {
373b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            final Thread blocked = Thread.currentThread();
374b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            signal.setOnCancelListener(new OnCancelListener() {
375b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey                @Override
376b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey                public void onCancel() {
377b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey                    blocked.interrupt();
378b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey                }
379b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            });
380b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            signal.throwIfCanceled();
381b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        }
382b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey
383b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        try {
384b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            Thread.sleep(timeoutMillis);
385b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        } catch (InterruptedException e) {
386b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        }
387b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey
388b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey        if (signal != null) {
389b3d42635aafacd80b1e1d257338ea6abb93d22c3Jeff Sharkey            signal.throwIfCanceled();
390d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey        }
391d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey    }
392d01571e6d4e1c403534e19142720530d324eac9bJeff Sharkey
393de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey    private static void includeFile(MatrixCursor result, String docId, int flags) {
394954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        final RowBuilder row = result.newRow();
395b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_DOCUMENT_ID, docId);
396b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_DISPLAY_NAME, docId);
397b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey        row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
398de2b22fbc60b29dd8af60cf05862066c04559dc0Jeff Sharkey        row.add(Document.COLUMN_FLAGS, flags);
399954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey
400954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        if (MY_DOC_ID.equals(docId)) {
401b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey            row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
40204d45a0bdd47c243ad7ccb179aa52a83e7bf45dbJeff Sharkey            row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_SUPPORTS_CREATE);
403954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        } else if (MY_DOC_NULL.equals(docId)) {
404954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey            // No MIME type
405954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        } else {
406b7757a6b32edea62a1a9a803ad83579220f26100Jeff Sharkey            row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream");
407954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey        }
408954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey    }
409954be0232655d316bc5decbbd35579af902c75c2Jeff Sharkey}
410