diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index e83c72779e672..39b453d381c32 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -72,6 +72,7 @@ public class ContentProviderClient { throws RemoteException { ICancellationSignal remoteCancellationSignal = null; if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); remoteCancellationSignal = mContentProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } @@ -208,6 +209,7 @@ public class ContentProviderClient { throws RemoteException, FileNotFoundException { ICancellationSignal remoteSignal = null; if (signal != null) { + signal.throwIfCanceled(); remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } @@ -244,6 +246,7 @@ public class ContentProviderClient { throws RemoteException, FileNotFoundException { ICancellationSignal remoteSignal = null; if (signal != null) { + signal.throwIfCanceled(); remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } @@ -269,6 +272,7 @@ public class ContentProviderClient { throws RemoteException, FileNotFoundException { ICancellationSignal remoteSignal = null; if (signal != null) { + signal.throwIfCanceled(); remoteSignal = mContentProvider.createCancellationSignal(); signal.setRemote(remoteSignal); } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 43120c4c8901a..37ce065a5cd86 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -19,6 +19,7 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static libcore.io.OsConstants.SEEK_SET; +import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -34,16 +35,18 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; +import android.os.RemoteException; import android.util.Log; import com.google.android.collect.Lists; import libcore.io.ErrnoException; -import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; +import java.io.BufferedInputStream; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.IOException; import java.util.List; @@ -76,6 +79,11 @@ public final class DocumentsContract { /** {@hide} */ public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; + /** + * Buffer is large enough to rewind past any EXIF headers. + */ + private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES); + /** * Constants related to a document, including {@link Cursor} columns names * and flags. @@ -642,35 +650,47 @@ public final class DocumentsContract { */ public static Bitmap getDocumentThumbnail( ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + return getDocumentThumbnail(client, documentUri, size, signal); + } catch (RemoteException e) { + return null; + } finally { + ContentProviderClient.closeQuietly(client); + } + } + + /** {@hide} */ + public static Bitmap getDocumentThumbnail( + ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) + throws RemoteException { final Bundle openOpts = new Bundle(); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { - afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); + afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); final FileDescriptor fd = afd.getFileDescriptor(); final long offset = afd.getStartOffset(); - final long length = afd.getDeclaredLength(); - // Some thumbnails might be a region inside a larger file, such as - // an EXIF thumbnail. Since BitmapFactory aggressively seeks around - // the entire file, we read the region manually. - byte[] region = null; - if (offset > 0 && length <= 64 * KB_IN_BYTES) { - region = new byte[(int) length]; + // Try seeking on the returned FD, since it gives us the most + // optimal decode path; otherwise fall back to buffering. + BufferedInputStream is = null; + try { Libcore.os.lseek(fd, offset, SEEK_SET); - if (IoBridge.read(fd, region, 0, region.length) != region.length) { - region = null; - } + } catch (ErrnoException e) { + is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); + is.mark(THUMBNAIL_BUFFER_SIZE); } // We requested a rough thumbnail size, but the remote size may have // returned something giant, so defensively scale down as needed. final BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; - if (region != null) { - BitmapFactory.decodeByteArray(region, 0, region.length, opts); + if (is != null) { + BitmapFactory.decodeStream(is, null, opts); } else { BitmapFactory.decodeFileDescriptor(fd, null, opts); } @@ -681,14 +701,17 @@ public final class DocumentsContract { opts.inJustDecodeBounds = false; opts.inSampleSize = Math.min(widthSample, heightSample); Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); - if (region != null) { - return BitmapFactory.decodeByteArray(region, 0, region.length, opts); + if (is != null) { + is.reset(); + return BitmapFactory.decodeStream(is, null, opts); } else { + try { + Libcore.os.lseek(fd, offset, SEEK_SET); + } catch (ErrnoException e) { + e.rethrowAsIOException(); + } return BitmapFactory.decodeFileDescriptor(fd, null, opts); } - } catch (ErrnoException e) { - Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); - return null; } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; @@ -709,13 +732,25 @@ public final class DocumentsContract { */ public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, String mimeType, String displayName) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + parentDocumentUri.getAuthority()); + try { + return createDocument(client, parentDocumentUri, mimeType, displayName); + } finally { + ContentProviderClient.closeQuietly(client); + } + } + + /** {@hide} */ + public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, + String mimeType, String displayName) { final Bundle in = new Bundle(); in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); in.putString(Document.COLUMN_MIME_TYPE, mimeType); in.putString(Document.COLUMN_DISPLAY_NAME, displayName); try { - final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); + final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); return buildDocumentUri( parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); } catch (Exception e) { @@ -730,11 +765,22 @@ public final class DocumentsContract { * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} */ public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + return deleteDocument(client, documentUri); + } finally { + ContentProviderClient.closeQuietly(client); + } + } + + /** {@hide} */ + public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) { final Bundle in = new Bundle(); in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); try { - final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); + final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in); return true; } catch (Exception e) { Log.w(TAG, "Failed to delete document", e); diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java index 014c664d1febe..a917e3fb7d5a1 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java @@ -17,10 +17,18 @@ package com.android.externalstorage; import android.content.ContentResolver; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Point; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; @@ -31,7 +39,14 @@ import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.util.Log; +import libcore.io.IoUtils; +import libcore.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.lang.ref.WeakReference; public class TestDocumentsProvider extends DocumentsProvider { @@ -85,7 +100,7 @@ public class TestDocumentsProvider extends DocumentsProvider { if (CRASH_DOCUMENT) System.exit(12); final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - includeFile(result, documentId); + includeFile(result, documentId, 0); return result; } @@ -122,12 +137,12 @@ public class TestDocumentsProvider extends DocumentsProvider { public boolean includeIfFinished(MatrixCursor result) { Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished); if (mFinished) { - includeFile(result, "_networkfile1"); - includeFile(result, "_networkfile2"); - includeFile(result, "_networkfile3"); - includeFile(result, "_networkfile4"); - includeFile(result, "_networkfile5"); - includeFile(result, "_networkfile6"); + includeFile(result, "_networkfile1", 0); + includeFile(result, "_networkfile2", 0); + includeFile(result, "_networkfile3", 0); + includeFile(result, "_networkfile4", 0); + includeFile(result, "_networkfile5", 0); + includeFile(result, "_networkfile6", 0); return true; } else { return false; @@ -162,11 +177,11 @@ public class TestDocumentsProvider extends DocumentsProvider { result.setNotificationUri(resolver, notifyUri); // Always include local results - includeFile(result, MY_DOC_NULL); - includeFile(result, "localfile1"); - includeFile(result, "localfile2"); - includeFile(result, "localfile3"); - includeFile(result, "localfile4"); + includeFile(result, MY_DOC_NULL, 0); + includeFile(result, "localfile1", 0); + includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL); + includeFile(result, "localfile3", 0); + includeFile(result, "localfile4", 0); synchronized (this) { // Try picking up an existing network fetch @@ -217,7 +232,8 @@ public class TestDocumentsProvider extends DocumentsProvider { SystemClock.sleep(3000); final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - includeFile(result, "It was /worth/ the_wait for?the file:with the&incredibly long name"); + includeFile( + result, "It was /worth/ the_wait for?the file:with the&incredibly long name", 0); return result; } @@ -227,16 +243,52 @@ public class TestDocumentsProvider extends DocumentsProvider { throw new FileNotFoundException(); } + @Override + public AssetFileDescriptor openDocumentThumbnail( + String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { + final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + final Paint paint = new Paint(); + paint.setColor(Color.BLUE); + canvas.drawColor(Color.RED); + canvas.drawLine(0, 0, 32, 32, paint); + + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitmap.compress(CompressFormat.JPEG, 50, bos); + + final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + try { + final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createReliablePipe(); + new AsyncTask() { + @Override + protected Object doInBackground(Object... params) { + final FileOutputStream fos = new FileOutputStream(fds[1].getFileDescriptor()); + try { + Streams.copy(bis, fos); + } catch (IOException e) { + throw new RuntimeException(e); + } + IoUtils.closeQuietly(fds[1]); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + return new AssetFileDescriptor(fds[0], 0, AssetFileDescriptor.UNKNOWN_LENGTH); + } catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); + } + } + @Override public boolean onCreate() { return true; } - private static void includeFile(MatrixCursor result, String docId) { + private static void includeFile(MatrixCursor result, String docId, int flags) { final RowBuilder row = result.newRow(); row.add(Document.COLUMN_DOCUMENT_ID, docId); row.add(Document.COLUMN_DISPLAY_NAME, docId); row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); + row.add(Document.COLUMN_FLAGS, flags); if (MY_DOC_ID.equals(docId)) { row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);