Merge "Handle pipe thumbnails, acquire unstable refs." into klp-dev

This commit is contained in:
Jeff Sharkey
2013-09-12 16:00:40 +00:00
committed by Android (Google) Code Review
3 changed files with 137 additions and 35 deletions

View File

@@ -72,6 +72,7 @@ public class ContentProviderClient {
throws RemoteException { throws RemoteException {
ICancellationSignal remoteCancellationSignal = null; ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) { if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = mContentProvider.createCancellationSignal(); remoteCancellationSignal = mContentProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal); cancellationSignal.setRemote(remoteCancellationSignal);
} }
@@ -208,6 +209,7 @@ public class ContentProviderClient {
throws RemoteException, FileNotFoundException { throws RemoteException, FileNotFoundException {
ICancellationSignal remoteSignal = null; ICancellationSignal remoteSignal = null;
if (signal != null) { if (signal != null) {
signal.throwIfCanceled();
remoteSignal = mContentProvider.createCancellationSignal(); remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal); signal.setRemote(remoteSignal);
} }
@@ -244,6 +246,7 @@ public class ContentProviderClient {
throws RemoteException, FileNotFoundException { throws RemoteException, FileNotFoundException {
ICancellationSignal remoteSignal = null; ICancellationSignal remoteSignal = null;
if (signal != null) { if (signal != null) {
signal.throwIfCanceled();
remoteSignal = mContentProvider.createCancellationSignal(); remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal); signal.setRemote(remoteSignal);
} }
@@ -269,6 +272,7 @@ public class ContentProviderClient {
throws RemoteException, FileNotFoundException { throws RemoteException, FileNotFoundException {
ICancellationSignal remoteSignal = null; ICancellationSignal remoteSignal = null;
if (signal != null) { if (signal != null) {
signal.throwIfCanceled();
remoteSignal = mContentProvider.createCancellationSignal(); remoteSignal = mContentProvider.createCancellationSignal();
signal.setRemote(remoteSignal); signal.setRemote(remoteSignal);
} }

View File

@@ -19,6 +19,7 @@ package android.provider;
import static android.net.TrafficStats.KB_IN_BYTES; import static android.net.TrafficStats.KB_IN_BYTES;
import static libcore.io.OsConstants.SEEK_SET; import static libcore.io.OsConstants.SEEK_SET;
import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@@ -34,16 +35,18 @@ import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.RemoteException;
import android.util.Log; import android.util.Log;
import com.google.android.collect.Lists; import com.google.android.collect.Lists;
import libcore.io.ErrnoException; import libcore.io.ErrnoException;
import libcore.io.IoBridge;
import libcore.io.IoUtils; import libcore.io.IoUtils;
import libcore.io.Libcore; import libcore.io.Libcore;
import java.io.BufferedInputStream;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@@ -76,6 +79,11 @@ public final class DocumentsContract {
/** {@hide} */ /** {@hide} */
public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; 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 * Constants related to a document, including {@link Cursor} columns names
* and flags. * and flags.
@@ -642,35 +650,47 @@ public final class DocumentsContract {
*/ */
public static Bitmap getDocumentThumbnail( public static Bitmap getDocumentThumbnail(
ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 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(); final Bundle openOpts = new Bundle();
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
AssetFileDescriptor afd = null; AssetFileDescriptor afd = null;
try { try {
afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
final FileDescriptor fd = afd.getFileDescriptor(); final FileDescriptor fd = afd.getFileDescriptor();
final long offset = afd.getStartOffset(); final long offset = afd.getStartOffset();
final long length = afd.getDeclaredLength();
// Some thumbnails might be a region inside a larger file, such as // Try seeking on the returned FD, since it gives us the most
// an EXIF thumbnail. Since BitmapFactory aggressively seeks around // optimal decode path; otherwise fall back to buffering.
// the entire file, we read the region manually. BufferedInputStream is = null;
byte[] region = null; try {
if (offset > 0 && length <= 64 * KB_IN_BYTES) {
region = new byte[(int) length];
Libcore.os.lseek(fd, offset, SEEK_SET); Libcore.os.lseek(fd, offset, SEEK_SET);
if (IoBridge.read(fd, region, 0, region.length) != region.length) { } catch (ErrnoException e) {
region = null; 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 // We requested a rough thumbnail size, but the remote size may have
// returned something giant, so defensively scale down as needed. // returned something giant, so defensively scale down as needed.
final BitmapFactory.Options opts = new BitmapFactory.Options(); final BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true; opts.inJustDecodeBounds = true;
if (region != null) { if (is != null) {
BitmapFactory.decodeByteArray(region, 0, region.length, opts); BitmapFactory.decodeStream(is, null, opts);
} else { } else {
BitmapFactory.decodeFileDescriptor(fd, null, opts); BitmapFactory.decodeFileDescriptor(fd, null, opts);
} }
@@ -681,14 +701,17 @@ public final class DocumentsContract {
opts.inJustDecodeBounds = false; opts.inJustDecodeBounds = false;
opts.inSampleSize = Math.min(widthSample, heightSample); opts.inSampleSize = Math.min(widthSample, heightSample);
Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
if (region != null) { if (is != null) {
return BitmapFactory.decodeByteArray(region, 0, region.length, opts); is.reset();
return BitmapFactory.decodeStream(is, null, opts);
} else { } else {
try {
Libcore.os.lseek(fd, offset, SEEK_SET);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
return BitmapFactory.decodeFileDescriptor(fd, null, opts); return BitmapFactory.decodeFileDescriptor(fd, null, opts);
} }
} catch (ErrnoException e) {
Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
return null;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
return null; return null;
@@ -709,13 +732,25 @@ public final class DocumentsContract {
*/ */
public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
String mimeType, String displayName) { 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(); final Bundle in = new Bundle();
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
in.putString(Document.COLUMN_MIME_TYPE, mimeType); in.putString(Document.COLUMN_MIME_TYPE, mimeType);
in.putString(Document.COLUMN_DISPLAY_NAME, displayName); in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
try { try {
final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
return buildDocumentUri( return buildDocumentUri(
parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
} catch (Exception e) { } catch (Exception e) {
@@ -730,11 +765,22 @@ public final class DocumentsContract {
* @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
*/ */
public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 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(); final Bundle in = new Bundle();
in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
try { try {
final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Failed to delete document", e); Log.w(TAG, "Failed to delete document", e);

View File

@@ -17,10 +17,18 @@
package com.android.externalstorage; package com.android.externalstorage;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder; 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.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
@@ -31,7 +39,14 @@ import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider; import android.provider.DocumentsProvider;
import android.util.Log; 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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
public class TestDocumentsProvider extends DocumentsProvider { public class TestDocumentsProvider extends DocumentsProvider {
@@ -85,7 +100,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
if (CRASH_DOCUMENT) System.exit(12); if (CRASH_DOCUMENT) System.exit(12);
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
includeFile(result, documentId); includeFile(result, documentId, 0);
return result; return result;
} }
@@ -122,12 +137,12 @@ public class TestDocumentsProvider extends DocumentsProvider {
public boolean includeIfFinished(MatrixCursor result) { public boolean includeIfFinished(MatrixCursor result) {
Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished); Log.d(TAG, hashCode() + ": includeIfFinished() found " + mFinished);
if (mFinished) { if (mFinished) {
includeFile(result, "_networkfile1"); includeFile(result, "_networkfile1", 0);
includeFile(result, "_networkfile2"); includeFile(result, "_networkfile2", 0);
includeFile(result, "_networkfile3"); includeFile(result, "_networkfile3", 0);
includeFile(result, "_networkfile4"); includeFile(result, "_networkfile4", 0);
includeFile(result, "_networkfile5"); includeFile(result, "_networkfile5", 0);
includeFile(result, "_networkfile6"); includeFile(result, "_networkfile6", 0);
return true; return true;
} else { } else {
return false; return false;
@@ -162,11 +177,11 @@ public class TestDocumentsProvider extends DocumentsProvider {
result.setNotificationUri(resolver, notifyUri); result.setNotificationUri(resolver, notifyUri);
// Always include local results // Always include local results
includeFile(result, MY_DOC_NULL); includeFile(result, MY_DOC_NULL, 0);
includeFile(result, "localfile1"); includeFile(result, "localfile1", 0);
includeFile(result, "localfile2"); includeFile(result, "localfile2", Document.FLAG_SUPPORTS_THUMBNAIL);
includeFile(result, "localfile3"); includeFile(result, "localfile3", 0);
includeFile(result, "localfile4"); includeFile(result, "localfile4", 0);
synchronized (this) { synchronized (this) {
// Try picking up an existing network fetch // Try picking up an existing network fetch
@@ -217,7 +232,8 @@ public class TestDocumentsProvider extends DocumentsProvider {
SystemClock.sleep(3000); SystemClock.sleep(3000);
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); 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; return result;
} }
@@ -227,16 +243,52 @@ public class TestDocumentsProvider extends DocumentsProvider {
throw new FileNotFoundException(); 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<Object, Object, Object>() {
@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 @Override
public boolean onCreate() { public boolean onCreate() {
return true; 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(); final RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId); row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, docId); row.add(Document.COLUMN_DISPLAY_NAME, docId);
row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis()); row.add(Document.COLUMN_LAST_MODIFIED, System.currentTimeMillis());
row.add(Document.COLUMN_FLAGS, flags);
if (MY_DOC_ID.equals(docId)) { if (MY_DOC_ID.equals(docId)) {
row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);