Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit de2b22fb authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Handle pipe thumbnails, acquire unstable refs.

Support decoding thumbnails delivered over pipes by wrapping in a
buffered stream.  Also switch to using unstable provider references
to avoid crashing DocumentsUI.

Bug: 10516148, 10510851
Change-Id: I85f6eeaca70c97742bf79656d1d0c6da381fdd47
parent aca405cd
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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);
        }
+68 −22
Original line number Diff line number Diff line
@@ -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);
+66 −14
Original line number Diff line number Diff line
@@ -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<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
    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);