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

Commit 1e119c43 authored by cd /gitc/manifest-rw/master-gitc-merge's avatar cd /gitc/manifest-rw/master-gitc-merge
Browse files

resolve merge conflicts of db05d140 to master

Change-Id: Ia83c7db466e5a9bb2a44db91a3671ee555abc1e0
parents f42bb04a db05d140
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.provider.DocumentsContract.Document;
import android.util.Log;

import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.FilteringCursorWrapper;
@@ -94,6 +95,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
        Cursor cursor;
        try {
            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
            if (mDoc.isInArchive()) {
                ArchivesProvider.acquireArchive(client, mUri);
            }
            result.client = client;

            Bundle queryArgs = new Bundle();
            mModel.addQuerySortArgs(queryArgs);

@@ -122,8 +128,6 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
            }

            cursor = mModel.sortCursor(cursor);

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

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

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

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

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

import com.android.documentsui.R;
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
 * synchronization.
 */
public class ArchivesProvider extends DocumentsProvider implements Closeable {
public class ArchivesProvider extends DocumentsProvider {
    public static final String AUTHORITY = "com.android.documentsui.archives";

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

    @GuardedBy("mArchives")
    private final LruCache<Key, Loader> mArchives =
            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();
                    }
                }
            };
    private final Map<Key, Loader> mArchives = new HashMap<Key, Loader>();

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

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

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

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

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

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

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

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

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

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

    @Override
    public AssetFileDescriptor openDocumentThumbnail(
            String documentId, Point sizeHint, final CancellationSignal signal)
            throws FileNotFoundException {
        Loader loader = null;
        try {
            loader = obtainInstance(documentId);
        final Loader loader = getLoaderOrThrow(documentId);
        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(),
                "Mismatching authority. Expected: %s, actual: %s.");
        final String documentId = DocumentsContract.getDocumentId(archiveUri);
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);

        try (final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                AUTHORITY)) {
            client.call(METHOD_CLOSE_ARCHIVE, documentId, null);
        try {
            client.call(METHOD_ACQUIRE_ARCHIVE, documentId, null);
        } 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) {
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        synchronized (mArchives) {
            mArchives.remove(Key.fromArchiveId(archiveId));
    public static void releaseArchive(ContentProviderClient client, Uri archiveUri) {
        Archive.MorePreconditions.checkArgumentEquals(AUTHORITY, archiveUri.getAuthority(),
                "Mismatching authority. Expected: %s, actual: %s.");
        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
     * operations on each opened archive are finished.
     * The archive won't close until all clients release it.
     */
    @Override
    // TODO: Wire close() to call().
    public void close() {
    private void acquireArchive(String documentId) {
        final ArchiveId archiveId = ArchiveId.fromDocumentId(documentId);
        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) {
            loader = getInstanceUncheckedLocked(documentId);
            loader.getReadLock().lock();
            final Loader loader = mArchives.get(key);
            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 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) {
                                final Loader currentLoader = mArchives.get(key);
                                if (currentLoader == loader) {
                                    mArchives.remove(key);
            final Loader loader = mArchives.get(key);
            if (loader == null) {
                throw new IllegalStateException("Archive not acquired.");
            }
                            }
                        }
                    });
        }

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

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

/**
 * 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_OPENED = 1;
    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 Uri mArchiveUri;
    private final int mAccessMode;
    private final Uri mNotificationUri;
    private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    private final Object mStatusLock = new Object();
    @GuardedBy("mStatusLock")
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private int mStatus = STATUS_OPENING;
    @GuardedBy("mLock")
    private int mRefCount = 0;
    private Archive mArchive = null;

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

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

        // Once loading the archive failed, do not to retry opening it until the
        // archive file has changed (the loader is deleted once we receive
        // a notification about the archive file being changed).
        synchronized (mStatusLock) {
            if (mStatus == STATUS_FAILED) {
        synchronized (mLock) {
            if (mStatus != STATUS_OPENING) {
                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 {
                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;
                }
            }
        } catch (IOException | RuntimeException e) {
            Log.e(TAG, "Failed to open the archive.", e);
            synchronized (mStatusLock) {
            synchronized (mLock) {
                mStatus = STATUS_FAILED;
            }
            throw new IllegalStateException("Failed to open the archive.", e);
@@ -115,16 +120,34 @@ public class Loader {
    }

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

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

    Lock getWriteLock() {
        return mLock.writeLock();
    void release() {
        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.
                }
            }
        }
    }
}
+13 −2
Original line number Diff line number Diff line
@@ -107,10 +107,15 @@ final class CompressJob extends CopyJob {
        try {
            mDstInfo = DocumentInfo.fromUri(resolver, ArchivesProvider.buildUriForArchive(
                    archiveUri, ParcelFileDescriptor.MODE_WRITE_ONLY));
            ArchivesProvider.acquireArchive(getClient(mDstInfo), mDstInfo.derivedUri);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Failed to create dstInfo.", e);
            failureCount = mResourceUris.getItemCount();
            return false;
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to acquire the archive.", e);
            failureCount = mResourceUris.getItemCount();
            return false;
        }

        return true;
@@ -118,9 +123,15 @@ final class CompressJob extends CopyJob {

    @Override
    void finish() {
        final ContentResolver resolver = appContext.getContentResolver();
        ArchivesProvider.closeArchive(resolver, mDstInfo.derivedUri);
        try {
            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.

        super.finish();
    }

    /**
Loading