TestDocumentsProvider.java revision de2b22fbc60b29dd8af60cf05862066c04559dc0
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 17package com.android.externalstorage; 18 19import android.content.ContentResolver; 20import android.content.res.AssetFileDescriptor; 21import android.database.Cursor; 22import android.database.MatrixCursor; 23import android.database.MatrixCursor.RowBuilder; 24import android.graphics.Bitmap; 25import android.graphics.Bitmap.CompressFormat; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.Paint; 29import android.graphics.Point; 30import android.net.Uri; 31import android.os.AsyncTask; 32import android.os.Bundle; 33import android.os.CancellationSignal; 34import android.os.ParcelFileDescriptor; 35import android.os.SystemClock; 36import android.provider.DocumentsContract; 37import android.provider.DocumentsContract.Document; 38import android.provider.DocumentsContract.Root; 39import android.provider.DocumentsProvider; 40import android.util.Log; 41 42import libcore.io.IoUtils; 43import libcore.io.Streams; 44 45import java.io.ByteArrayInputStream; 46import java.io.ByteArrayOutputStream; 47import java.io.FileNotFoundException; 48import java.io.FileOutputStream; 49import java.io.IOException; 50import java.lang.ref.WeakReference; 51 52public class TestDocumentsProvider extends DocumentsProvider { 53 private static final String TAG = "TestDocuments"; 54 55 private static final boolean CRASH_ROOTS = false; 56 private static final boolean CRASH_DOCUMENT = false; 57 58 private static final String MY_ROOT_ID = "myRoot"; 59 private static final String MY_DOC_ID = "myDoc"; 60 private static final String MY_DOC_NULL = "myNull"; 61 62 private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { 63 Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, 64 Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, 65 Root.COLUMN_AVAILABLE_BYTES, 66 }; 67 68 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { 69 Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, 70 Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, 71 }; 72 73 private static String[] resolveRootProjection(String[] projection) { 74 return projection != null ? projection : DEFAULT_ROOT_PROJECTION; 75 } 76 77 private static String[] resolveDocumentProjection(String[] projection) { 78 return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; 79 } 80 81 @Override 82 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 83 if (CRASH_ROOTS) System.exit(12); 84 85 final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); 86 final RowBuilder row = result.newRow(); 87 row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID); 88 row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE); 89 row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); 90 row.add(Root.COLUMN_TITLE, "_Test title which is really long"); 91 row.add(Root.COLUMN_SUMMARY, "_Summary which is also super long text"); 92 row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID); 93 row.add(Root.COLUMN_AVAILABLE_BYTES, 1024); 94 return result; 95 } 96 97 @Override 98 public Cursor queryDocument(String documentId, String[] projection) 99 throws FileNotFoundException { 100 if (CRASH_DOCUMENT) System.exit(12); 101 102 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 103 includeFile(result, documentId, 0); 104 return result; 105 } 106 107 /** 108 * Holds any outstanding or finished "network" fetching. 109 */ 110 private WeakReference<CloudTask> mTask; 111 112 private static class CloudTask implements Runnable { 113 114 private final ContentResolver mResolver; 115 private final Uri mNotifyUri; 116 117 private volatile boolean mFinished; 118 119 public CloudTask(ContentResolver resolver, Uri notifyUri) { 120 mResolver = resolver; 121 mNotifyUri = notifyUri; 122 } 123 124 @Override 125 public void run() { 126 // Pretend to do some network 127 Log.d(TAG, hashCode() + ": pretending to do some network!"); 128 SystemClock.sleep(2000); 129 Log.d(TAG, hashCode() + ": network done!"); 130 131 mFinished = true; 132 133 // Tell anyone remotely they should requery 134 mResolver.notifyChange(mNotifyUri, null, false); 135 } 136 137 public boolean includeIfFinished(MatrixCursor result) { 138 Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished); 139 if (mFinished) { 140 includeFile(result, "_networkfile1", 0); 141 includeFile(result, "_networkfile2", 0); 142 includeFile(result, "_networkfile3", 0); 143 includeFile(result, "_networkfile4", 0); 144 includeFile(result, "_networkfile5", 0); 145 includeFile(result, "_networkfile6", 0); 146 return true; 147 } else { 148 return false; 149 } 150 } 151 } 152 153 private static class CloudCursor extends MatrixCursor { 154 public Object keepAlive; 155 public final Bundle extras = new Bundle(); 156 157 public CloudCursor(String[] columnNames) { 158 super(columnNames); 159 } 160 161 @Override 162 public Bundle getExtras() { 163 return extras; 164 } 165 } 166 167 @Override 168 public Cursor queryChildDocuments( 169 String parentDocumentId, String[] projection, String sortOrder) 170 throws FileNotFoundException { 171 172 final ContentResolver resolver = getContext().getContentResolver(); 173 final Uri notifyUri = DocumentsContract.buildDocumentUri( 174 "com.example.documents", parentDocumentId); 175 176 CloudCursor result = new CloudCursor(resolveDocumentProjection(projection)); 177 result.setNotificationUri(resolver, notifyUri); 178 179 // Always include local results 180 includeFile(result, MY_DOC_NULL, 0); 181 includeFile(result, "localfile1", 0); 182 includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL); 183 includeFile(result, "localfile3", 0); 184 includeFile(result, "localfile4", 0); 185 186 synchronized (this) { 187 // Try picking up an existing network fetch 188 CloudTask task = mTask != null ? mTask.get() : null; 189 if (task == null) { 190 Log.d(TAG, "No network task found; starting!"); 191 task = new CloudTask(resolver, notifyUri); 192 mTask = new WeakReference<CloudTask>(task); 193 new Thread(task).start(); 194 195 // Aggressively try freeing weak reference above 196 new Thread() { 197 @Override 198 public void run() { 199 while (mTask.get() != null) { 200 SystemClock.sleep(200); 201 System.gc(); 202 System.runFinalization(); 203 } 204 Log.d(TAG, "AHA! THE CLOUD TASK WAS GC'ED!"); 205 } 206 }.start(); 207 } 208 209 // Blend in cloud results if ready 210 if (task.includeIfFinished(result)) { 211 result.extras.putString(DocumentsContract.EXTRA_INFO, 212 "Everything Went Better Than Expected and this message is quite " 213 + "long and verbose and maybe even too long"); 214 result.extras.putString(DocumentsContract.EXTRA_ERROR, 215 "But then again, maybe our server ran into an error, which means " 216 + "we're going to have a bad time"); 217 } else { 218 result.extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); 219 } 220 221 // Tie the network fetch to the cursor GC lifetime 222 result.keepAlive = task; 223 224 return result; 225 } 226 } 227 228 @Override 229 public Cursor queryRecentDocuments(String rootId, String[] projection) 230 throws FileNotFoundException { 231 // Pretend to take a super long time to respond 232 SystemClock.sleep(3000); 233 234 final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 235 includeFile( 236 result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0); 237 return result; 238 } 239 240 @Override 241 public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) 242 throws FileNotFoundException { 243 throw new FileNotFoundException(); 244 } 245 246 @Override 247 public AssetFileDescriptor openDocumentThumbnail( 248 String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { 249 final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); 250 final Canvas canvas = new Canvas(bitmap); 251 final Paint paint = new Paint(); 252 paint.setColor(Color.BLUE); 253 canvas.drawColor(Color.RED); 254 canvas.drawLine(0, 0, 32, 32, paint); 255 256 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 257 bitmap.compress(CompressFormat.JPEG, 50, bos); 258 259 final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); 260 try { 261 final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe(); 262 new AsyncTask<Object, Object, Object>() { 263 @Override 264 protected Object doInBackground(Object... params) { 265 final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor()); 266 try { 267 Streams.copy(bis, fos); 268 } catch (IOException e) { 269 throw new RuntimeException(e); 270 } 271 IoUtils.closeQuietly(fds[1]); 272 return null; 273 } 274 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 275 return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH); 276 } catch (IOException e) { 277 throw new FileNotFoundException(e.getMessage()); 278 } 279 } 280 281 @Override 282 public boolean onCreate() { 283 return true; 284 } 285 286 private static void includeFile(MatrixCursor result, String docId, int flags) { 287 final RowBuilder row = result.newRow(); 288 row.add(Document.COLUMN_DOCUMENT_ID, docId); 289 row.add(Document.COLUMN_DISPLAY_NAME, docId); 290 row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); 291 row.add(Document.COLUMN_FLAGS, flags); 292 293 if (MY_DOC_ID.equals(docId)) { 294 row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); 295 } else if (MY_DOC_NULL.equals(docId)) { 296 // No MIME type 297 } else { 298 row.add(Document.COLUMN_MIME_TYPE, "application/octet-stream"); 299 } 300 } 301} 302