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

Commit db05d140 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Remove cache, and manually control archives lifecycle." into arc-apps

parents cb60057b b19061c6
Loading
Loading
Loading
Loading
+6 −2
Original line number Original line Diff line number Diff line
@@ -30,6 +30,7 @@ import android.os.RemoteException;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Document;
import android.util.Log;
import android.util.Log;


import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.FilteringCursorWrapper;
import com.android.documentsui.base.FilteringCursorWrapper;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.RootInfo;
@@ -92,6 +93,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
        Cursor cursor;
        Cursor cursor;
        try {
        try {
            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
            if (mDoc.isInArchive()) {
                ArchivesProvider.acquireArchive(client, mUri);
            }
            result.client = client;
            cursor = client.query(
            cursor = client.query(
                    mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal);
                    mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal);
            if (cursor == null) {
            if (cursor == null) {
@@ -108,8 +113,6 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
            }
            }


            cursor = mModel.sortCursor(cursor);
            cursor = mModel.sortCursor(cursor);

            result.client = client;
            result.cursor = cursor;
            result.cursor = cursor;
        } catch (Exception e) {
        } catch (Exception e) {
            Log.w(TAG, "Failed to query", e);
            Log.w(TAG, "Failed to query", e);
@@ -118,6 +121,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
            synchronized (this) {
            synchronized (this) {
                mSignal = null;
                mSignal = null;
            }
            }
            // TODO: Remove this call.
            ContentProviderClient.releaseQuietly(client);
            ContentProviderClient.releaseQuietly(client);
        }
        }


+5 −1
Original line number Original line Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.documentsui;
package com.android.documentsui;


import android.content.ContentProviderClient;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.database.Cursor;
import android.database.Cursor;


import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.DocumentInfo;


import libcore.io.IoUtils;
import libcore.io.IoUtils;
@@ -33,7 +35,9 @@ public class DirectoryResult implements AutoCloseable {
    @Override
    @Override
    public void close() {
    public void close() {
        IoUtils.closeQuietly(cursor);
        IoUtils.closeQuietly(cursor);
        ContentProviderClient.releaseQuietly(client);
        if (client != null && doc.isInArchive()) {
            ArchivesProvider.releaseArchive(client, doc.derivedUri);
        }
        cursor = null;
        cursor = null;
        client = null;
        client = null;
        doc = null;
        doc = null;
+97 −165
Original line number Original line Diff line number Diff line
@@ -35,7 +35,6 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsProvider;
import android.provider.DocumentsProvider;
import android.support.annotation.Nullable;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Log;
import android.util.LruCache;


import com.android.documentsui.R;
import com.android.documentsui.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
@@ -57,37 +56,28 @@ import java.util.concurrent.locks.Lock;
 * <p>This class is thread safe. All methods can be called on any thread without
 * <p>This class is thread safe. All methods can be called on any thread without
 * synchronization.
 * synchronization.
 */
 */
public class ArchivesProvider extends DocumentsProvider implements Closeable {
public class ArchivesProvider extends DocumentsProvider {
    public static final String AUTHORITY = "com.android.documentsui.archives";
    public static final String AUTHORITY = "com.android.documentsui.archives";


    private static final String TAG = "ArchivesProvider";
    private static final String TAG = "ArchivesProvider";
    private static final String METHOD_CLOSE_ARCHIVE = "closeArchive";
    private static final String METHOD_ACQUIRE_ARCHIVE = "acquireArchive";
    private static final int OPENED_ARCHIVES_CACHE_SIZE = 4;
    private static final String METHOD_RELEASE_ARCHIVE = "releaseArchive";
    private static final String[] ZIP_MIME_TYPES = {
    private static final String[] ZIP_MIME_TYPES = {
            "application/zip", "application/x-zip", "application/x-zip-compressed"
            "application/zip", "application/x-zip", "application/x-zip-compressed"
    };
    };


    @GuardedBy("mArchives")
    @GuardedBy("mArchives")
    private final LruCache<Key, Loader> mArchives =
    private final Map<Key, Loader> mArchives = new HashMap<Key, Loader>();
            new LruCache<Key, Loader>(OPENED_ARCHIVES_CACHE_SIZE) {
                @Override
                public void entryRemoved(boolean evicted, Key key,
                        Loader oldValue, Loader newValue) {
                    oldValue.getWriteLock().lock();
                    try {
                        oldValue.get().close();
                    } catch (IOException e) {
                        Log.e(TAG, "Closing archive failed.", e);
                    }finally {
                        oldValue.getWriteLock().unlock();
                    }
                }
            };


    @Override
    @Override
    public Bundle call(String method, String arg, Bundle extras) {
    public Bundle call(String method, String arg, Bundle extras) {
        if (METHOD_CLOSE_ARCHIVE.equals(method)) {
        if (METHOD_ACQUIRE_ARCHIVE.equals(method)) {
            closeArchive(arg);
            acquireArchive(arg);
            return null;
        }

        if (METHOD_RELEASE_ARCHIVE.equals(method)) {
            releaseArchive(arg);
            return null;
            return null;
        }
        }


@@ -109,9 +99,7 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
            @Nullable String sortOrder)
            @Nullable String sortOrder)
            throws FileNotFoundException {
            throws FileNotFoundException {
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        final int status = loader.getStatus();
        final int status = loader.getStatus();
        // If already loaded, then forward the request to the archive.
        // If already loaded, then forward the request to the archive.
        if (status == Loader.STATUS_OPENED) {
        if (status == Loader.STATUS_OPENED) {
@@ -140,9 +128,6 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
        cursor.setNotificationUri(getContext().getContentResolver(),
        cursor.setNotificationUri(getContext().getContentResolver(),
                buildUriForArchive(archiveId.mArchiveUri, archiveId.mAccessMode));
                buildUriForArchive(archiveId.mArchiveUri, archiveId.mAccessMode));
        return cursor;
        return cursor;
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
@@ -152,26 +137,14 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
            return Document.MIME_TYPE_DIR;
            return Document.MIME_TYPE_DIR;
        }
        }


        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        return loader.get().getDocumentType(documentId);
        return loader.get().getDocumentType(documentId);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
    public boolean isChildDocument(String parentDocumentId, String documentId) {
    public boolean isChildDocument(String parentDocumentId, String documentId) {
        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        return loader.get().isChildDocument(parentDocumentId, documentId);
        return loader.get().isChildDocument(parentDocumentId, documentId);
        } catch (FileNotFoundException e) {
            throw new IllegalStateException(e);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
@@ -201,52 +174,32 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
            }
            }
        }
        }


        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        return loader.get().queryDocument(documentId, projection);
        return loader.get().queryDocument(documentId, projection);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
    public String createDocument(
    public String createDocument(
            String parentDocumentId, String mimeType, String displayName)
            String parentDocumentId, String mimeType, String displayName)
            throws FileNotFoundException {
            throws FileNotFoundException {
        Loader loader = null;
        final Loader loader = getLoaderOrThrow(parentDocumentId);
        try {
            loader = obtainInstance(parentDocumentId);
        return loader.get().createDocument(parentDocumentId, mimeType, displayName);
        return loader.get().createDocument(parentDocumentId, mimeType, displayName);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
    public ParcelFileDescriptor openDocument(
    public ParcelFileDescriptor openDocument(
            String documentId, String mode, final CancellationSignal signal)
            String documentId, String mode, final CancellationSignal signal)
            throws FileNotFoundException {
            throws FileNotFoundException {
        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        return loader.get().openDocument(documentId, mode, signal);
        return loader.get().openDocument(documentId, mode, signal);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    @Override
    @Override
    public AssetFileDescriptor openDocumentThumbnail(
    public AssetFileDescriptor openDocumentThumbnail(
            String documentId, Point sizeHint, final CancellationSignal signal)
            String documentId, Point sizeHint, final CancellationSignal signal)
            throws FileNotFoundException {
            throws FileNotFoundException {
        Loader loader = null;
        final Loader loader = getLoaderOrThrow(documentId);
        try {
            loader = obtainInstance(documentId);
        return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
        return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
        } finally {
            releaseInstance(loader);
        }
    }
    }


    /**
    /**
@@ -273,102 +226,81 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
    }
    }


    /**
    /**
     * Closes an archive.
     * Acquires an archive.
     */
     */
    public static void closeArchive(ContentResolver resolver, Uri archiveUri) {
    public static void acquireArchive(ContentProviderClient client, Uri archiveUri) {
        Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(),
        Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(),
                "Mismatching authority. Expected: %s, actual: %s.");
                "Mismatching authority. Expected: %s, actual: %s.");
        final String documentId = DocumentsContract.getDocumentId(archiveUri);
        final String documentId = DocumentsContract.getDocumentId(archiveUri);
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);


        try (final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
        try {
                AUTHORITY)) {
            client.call(METHOD_ACQUIRE_ARCHIVE, documentId, null);
            client.call(METHOD_CLOSE_ARCHIVE, documentId, null);
        } catch (Exception e) {
        } catch (Exception e) {
            Log.w(TAG, "Failed to close archive.", e);
            Log.w(TAG, "Failed to acquire archive.", e);
        }
        }
    }
    }


    /**
    /**
     * Closes an archive.
     * Releases an archive.
     */
     */
    public void closeArchive(String documentId) {
    public static void releaseArchive(ContentProviderClient client, Uri archiveUri) {
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(),
        synchronized (mArchives) {
                "Mismatching authority. Expected: %s, actual: %s.");
            mArchives.remove(Key.fromArchiveId(archiveId));
        final String documentId = DocumentsContract.getDocumentId(archiveUri);

        try {
            client.call(METHOD_RELEASE_ARCHIVE, documentId, null);
        } catch (Exception e) {
            Log.w(TAG, "Failed to release archive.", e);
        }
        }
    }
    }


    /**
    /**
     * Closes the helper and disposes all existing archives. It will block until all ongoing
     * The archive won't close until all clients release it.
     * operations on each opened archive are finished.
     */
     */
    @Override
    private void acquireArchive(String documentId) {
    // TODO: Wire close() to call().
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
    public void close() {
        synchronized (mArchives) {
        synchronized (mArchives) {
            mArchives.evictAll();
            final Key key = Key.fromArchiveId(archiveId);
            Loader loader = mArchives.get(key);
            if (loader == null) {
                // TODO: Pass parent Uri so the loader can acquire the parent's notification Uri.
                loader = new Loader(getContext(), archiveId.mArchiveUri, archiveId.mAccessMode,
                        null);
                mArchives.put(key, loader);
            }
            loader.acquire();
            mArchives.put(key, loader);
        }
        }
    }
    }


    private Loader obtainInstance(String documentId) throws FileNotFoundException {
    /**
        Loader loader;
     * If all clients release the archive, then it will be closed.
     */
    private void releaseArchive(String documentId) {
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        final Key key = Key.fromArchiveId(archiveId);
        synchronized (mArchives) {
        synchronized (mArchives) {
            loader = getInstanceUncheckedLocked(documentId);
            final Loader loader = mArchives.get(key);
            loader.getReadLock().lock();
            loader.release();
            final int status = loader.getStatus();
            if (status == Loader.STATUS_CLOSED || status == Loader.STATUS_CLOSING) {
                mArchives.remove(key);
            }
            }
        return loader;
    }

    private void releaseInstance(@Nullable Loader loader) {
        if (loader != null) {
            loader.getReadLock().unlock();
        }
        }
    }
    }


    private Loader getInstanceUncheckedLocked(String documentId) throws FileNotFoundException {
    private Loader getLoaderOrThrow(String documentId) {
        final ArchiveId id = ArchiveId.fromDocumentId(documentId);
        final ArchiveId id = ArchiveId.fromDocumentId(documentId);
        final Key key = Key.fromArchiveId(id);
        final Key key = Key.fromArchiveId(id);
        final Loader existingLoader = mArchives.get(key);
        if (existingLoader != null) {
            return existingLoader;
        }

        final Cursor cursor = getContext().getContentResolver().query(
                id.mArchiveUri, new String[] { Document.COLUMN_MIME_TYPE }, null, null, null);
        if (cursor == null || cursor.getCount() == 0) {
            throw new FileNotFoundException("File not found." + id.mArchiveUri);
        }

        cursor.moveToFirst();
        final String mimeType = cursor.getString(cursor.getColumnIndex(
                Document.COLUMN_MIME_TYPE));
        Preconditions.checkArgument(isSupportedArchiveType(mimeType));
        final Uri notificationUri = cursor.getNotificationUri();
        final Loader loader = new Loader(getContext(), id.mArchiveUri, id.mAccessMode,
                notificationUri);

        // Remove the instance from mArchives collection once the archive file changes.
        if (notificationUri != null) {
            final LruCache<Key, Loader> finalArchives = mArchives;
            getContext().getContentResolver().registerContentObserver(notificationUri,
                    false,
                    new ContentObserver(null) {
                        @Override
                        public void onChange(boolean selfChange, Uri uri) {
        synchronized (mArchives) {
        synchronized (mArchives) {
                                final Loader currentLoader = mArchives.get(key);
            final Loader loader = mArchives.get(key);
                                if (currentLoader == loader) {
            if (loader == null) {
                                    mArchives.remove(key);
                throw new IllegalStateException("Archive not acquired.");
            }
            }
                            }
                        }
                    });
        }

        mArchives.put(key, loader);
            return loader;
            return loader;
        }
        }
    }


    private static class Key {
    private static class Key {
        Uri archiveUri;
        Uri archiveUri;
+42 −19
Original line number Original line Diff line number Diff line
@@ -28,7 +28,6 @@ import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
/**
 * Loads an instance of Archive lazily.
 * Loads an instance of Archive lazily.
@@ -39,16 +38,19 @@ public class Loader {
    public static final int STATUS_OPENING = 0;
    public static final int STATUS_OPENING = 0;
    public static final int STATUS_OPENED = 1;
    public static final int STATUS_OPENED = 1;
    public static final int STATUS_FAILED = 2;
    public static final int STATUS_FAILED = 2;
    public static final int STATUS_CLOSING = 3;
    public static final int STATUS_CLOSED = 4;


    private final Context mContext;
    private final Context mContext;
    private final Uri mArchiveUri;
    private final Uri mArchiveUri;
    private final int mAccessMode;
    private final int mAccessMode;
    private final Uri mNotificationUri;
    private final Uri mNotificationUri;
    private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    private final Object mStatusLock = new Object();
    private final Object mLock = new Object();
    @GuardedBy("mStatusLock")
    @GuardedBy("mLock")
    private int mStatus = STATUS_OPENING;
    private int mStatus = STATUS_OPENING;
    @GuardedBy("mLock")
    private int mRefCount = 0;
    private Archive mArchive = null;
    private Archive mArchive = null;


    Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri) {
    Loader(Context context, Uri archiveUri, int accessMode, Uri notificationUri) {
@@ -62,19 +64,16 @@ public class Loader {
    }
    }


    synchronized Archive get() {
    synchronized Archive get() {
        synchronized (mStatusLock) {
        synchronized (mLock) {
            if (mStatus == STATUS_OPENED) {
            if (mStatus == STATUS_OPENED) {
                return mArchive;
                return mArchive;
            }
            }
        }
        }


        // Once loading the archive failed, do not to retry opening it until the
        synchronized (mLock) {
        // archive file has changed (the loader is deleted once we receive
            if (mStatus != STATUS_OPENING) {
        // a notification about the archive file being changed).
        synchronized (mStatusLock) {
            if (mStatus == STATUS_FAILED) {
                throw new IllegalStateException(
                throw new IllegalStateException(
                        "Trying to perform an operation on an archive which failed to load.");
                        "Trying to perform an operation on an archive which is invalidated.");
            }
            }
        }
        }


@@ -94,12 +93,18 @@ public class Loader {
            } else {
            } else {
                throw new IllegalStateException("Access mode not supported.");
                throw new IllegalStateException("Access mode not supported.");
            }
            }
            synchronized (mStatusLock) {
            boolean closedDueToRefcount = false;
            synchronized (mLock) {
                if (mRefCount == 0) {
                    mArchive.close();
                    mStatus = STATUS_CLOSED;
                } else {
                    mStatus = STATUS_OPENED;
                    mStatus = STATUS_OPENED;
                }
                }
            }
        } catch (IOException | RuntimeException e) {
        } catch (IOException | RuntimeException e) {
            Log.e(TAG, "Failed to open the archive.", e);
            Log.e(TAG, "Failed to open the archive.", e);
            synchronized (mStatusLock) {
            synchronized (mLock) {
                mStatus = STATUS_FAILED;
                mStatus = STATUS_FAILED;
            }
            }
            throw new IllegalStateException("Failed to open the archive.", e);
            throw new IllegalStateException("Failed to open the archive.", e);
@@ -115,16 +120,34 @@ public class Loader {
    }
    }


    int getStatus() {
    int getStatus() {
        synchronized (mStatusLock) {
        synchronized (mLock) {
            return mStatus;
            return mStatus;
        }
        }
    }
    }


    Lock getReadLock() {
    void acquire() {
        return mLock.readLock();
        synchronized (mLock) {
            mRefCount++;
        }
    }
    }


    Lock getWriteLock() {
    void release() {
        return mLock.writeLock();
        synchronized (mLock) {
            mRefCount--;
            if (mRefCount == 0) {
                if (mStatus == STATUS_OPENED) {
                    try {
                        mArchive.close();
                        mStatus = STATUS_CLOSED;
                    } catch (IOException e) {
                        Log.e(TAG, "Failed to close the archive on release.", e);
                        mStatus = STATUS_FAILED;
                    }
                } else {
                    mStatus = STATUS_CLOSING;
                    // ::get() will close the archive once opened.
                }
            }
        }
    }
    }
}
}
+18 −3
Original line number Original line Diff line number Diff line
@@ -85,6 +85,10 @@ final class CompressJob extends CopyJob {


    @Override
    @Override
    public boolean setUp() {
    public boolean setUp() {
        if (!super.setUp()) {
            return false;
        }

        final ContentResolver resolver = appContext.getContentResolver();
        final ContentResolver resolver = appContext.getContentResolver();


        // TODO: Move this to DocumentsProvider.
        // TODO: Move this to DocumentsProvider.
@@ -95,20 +99,31 @@ final class CompressJob extends CopyJob {
        try {
        try {
            mDstInfo = DocumentInfo.fromUri(resolver, ArchivesProvider.buildUriForArchive(
            mDstInfo = DocumentInfo.fromUri(resolver, ArchivesProvider.buildUriForArchive(
                    archiveUri, ParcelFileDescriptor.MODE_WRITE_ONLY));
                    archiveUri, ParcelFileDescriptor.MODE_WRITE_ONLY));
            ArchivesProvider.acquireArchive(getClient(mDstInfo), mDstInfo.derivedUri);
        } catch (FileNotFoundException e) {
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Failed to create dstInfo.", e);
            Log.e(TAG, "Failed to create dstInfo.", e);
            failureCount = mResourceUris.getItemCount();
            failureCount = mResourceUris.getItemCount();
            return false;
            return false;
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to acquire the archive.", e);
            failureCount = mResourceUris.getItemCount();
            return false;
        }
        }


        return super.setUp();
        return true;
    }
    }


    @Override
    @Override
    void finish() {
    void finish() {
        final ContentResolver resolver = appContext.getContentResolver();
        try {
        ArchivesProvider.closeArchive(resolver, mDstInfo.derivedUri);
            ArchivesProvider.releaseArchive(getClient(mDstInfo), mDstInfo.derivedUri);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to release the archive.");
        }

        // TODO: Remove the archive file in case of an error.
        // TODO: Remove the archive file in case of an error.

        super.finish();
    }
    }


    /**
    /**
Loading