Loading src/com/android/documentsui/DirectoryLoader.java +6 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,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.DocumentInfo; import com.android.documentsui.base.FilteringCursorWrapper; import com.android.documentsui.base.RootInfo; Loading Loading @@ -92,6 +93,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { Cursor cursor; try { client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); if (mDoc.isInArchive()) { ArchivesProvider.acquireArchive(client, mUri); } result.client = client; cursor = client.query( mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal); if (cursor == null) { Loading @@ -108,8 +113,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); Loading @@ -118,6 +121,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { synchronized (this) { mSignal = null; } // TODO: Remove this call. ContentProviderClient.releaseQuietly(client); } Loading src/com/android/documentsui/DirectoryResult.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading src/com/android/documentsui/archives/ArchivesProvider.java +97 −165 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); } } /** Loading @@ -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; Loading src/com/android/documentsui/archives/Loader.java +42 −19 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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) { Loading @@ -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."); } } Loading @@ -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); Loading @@ -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. } } } } } src/com/android/documentsui/services/CompressJob.java +18 −3 Original line number Diff line number Diff line Loading @@ -85,6 +85,10 @@ final class CompressJob extends CopyJob { @Override public boolean setUp() { if (!super.setUp()) { return false; } final ContentResolver resolver = appContext.getContentResolver(); // TODO: Move this to DocumentsProvider. Loading @@ -95,20 +99,31 @@ 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 super.setUp(); return true; } @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 Loading
src/com/android/documentsui/DirectoryLoader.java +6 −2 Original line number Diff line number Diff line Loading @@ -30,6 +30,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.DocumentInfo; import com.android.documentsui.base.FilteringCursorWrapper; import com.android.documentsui.base.RootInfo; Loading Loading @@ -92,6 +93,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { Cursor cursor; try { client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority); if (mDoc.isInArchive()) { ArchivesProvider.acquireArchive(client, mUri); } result.client = client; cursor = client.query( mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal); if (cursor == null) { Loading @@ -108,8 +113,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); Loading @@ -118,6 +121,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { synchronized (this) { mSignal = null; } // TODO: Remove this call. ContentProviderClient.releaseQuietly(client); } Loading
src/com/android/documentsui/DirectoryResult.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading
src/com/android/documentsui/archives/ArchivesProvider.java +97 −165 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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); } } /** Loading @@ -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; Loading
src/com/android/documentsui/archives/Loader.java +42 −19 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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) { Loading @@ -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."); } } Loading @@ -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); Loading @@ -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. } } } } }
src/com/android/documentsui/services/CompressJob.java +18 −3 Original line number Diff line number Diff line Loading @@ -85,6 +85,10 @@ final class CompressJob extends CopyJob { @Override public boolean setUp() { if (!super.setUp()) { return false; } final ContentResolver resolver = appContext.getContentResolver(); // TODO: Move this to DocumentsProvider. Loading @@ -95,20 +99,31 @@ 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 super.setUp(); return true; } @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