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

Commit 40cec2ae authored by Tomasz Mikolajewski's avatar Tomasz Mikolajewski
Browse files

Handle broken archives correctly.

Test: Manually and automated tests.
Bug: 32260433
Change-Id: I2c701c34184b7bb2954e28954398e0634ee0c4a1
parent 21cf5edd
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -265,4 +265,7 @@
    <string name="documents_shortcut_label">Documents</string>
    <!-- Shortcut label of Downloads root folder -->
    <string name="downloads_shortcut_label">Downloads</string>

    <!-- Error message shown when an archive fails to load -->
    <string name="archive_loading_failed">Unable to open archive for browsing. File is either corrupt, or an unsupported format.</string>
</resources>
+32 −15
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.support.annotation.Nullable;
import android.util.Log;
import android.util.LruCache;

import com.android.documentsui.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

@@ -70,8 +71,6 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
                    oldValue.getWriteLock().lock();
                    try {
                        oldValue.get().close();
                    } catch (FileNotFoundException e) {
                        Log.e(TAG, "Failed to close an archive as it no longer exists.");
                    } finally {
                        oldValue.getWriteLock().unlock();
                    }
@@ -96,20 +95,34 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {
        Loader loader = null;
        try {
            loader = obtainInstance(documentId);
            if (loader.mArchive == null) {
            final int status = loader.getStatus();
            // If already loaded, then forward the request to the archive.
            if (status == Loader.STATUS_OPENED) {
                return loader.get().queryChildDocuments(documentId, projection, sortOrder);
            }

            final MatrixCursor cursor = new MatrixCursor(
                    projection != null ? projection : Archive.DEFAULT_PROJECTION);
            final Bundle bundle = new Bundle();

            switch (status) {
                case Loader.STATUS_OPENING:
                    bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
                    break;

                case Loader.STATUS_FAILED:
                    // Return an empty cursor with EXTRA_LOADING, which shows spinner
                    // in DocumentsUI. Once the archive is loaded, the notification will
                    // be sent, and the directory reloaded.
                final Bundle bundle = new Bundle();
                bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
                    bundle.putString(DocumentsContract.EXTRA_ERROR,
                            getContext().getString(R.string.archive_loading_failed));
                    break;
            }

            cursor.setExtras(bundle);
            cursor.setNotificationUri(getContext().getContentResolver(),
                    buildUriForArchive(archiveId.mArchiveUri));
            return cursor;
            }
            return loader.get().queryChildDocuments(documentId, projection, sortOrder);
        } finally {
            releaseInstance(loader);
        }
@@ -259,6 +272,10 @@ public class ArchivesProvider extends DocumentsProvider implements Closeable {

        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));
+39 −15
Original line number Diff line number Diff line
@@ -16,8 +16,11 @@

package com.android.documentsui.archives;

import com.android.internal.annotations.GuardedBy;

import android.content.Context;
import android.net.Uri;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
@@ -31,13 +34,21 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 * Loads an instance of Archive lazily.
 */
public class Loader {
    private static final String TAG = "Loader";

    public static final int STATUS_OPENING = 0;
    public static final int STATUS_OPENED = 1;
    public static final int STATUS_FAILED = 2;

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

    Loader(Context context, Uri archiveUri, Uri notificationUri) {
        this.mContext = context;
@@ -48,18 +59,21 @@ public class Loader {
        mExecutor.submit(this::get);
    }

    synchronized Archive get() throws FileNotFoundException {
        if (mArchive != null) {
    synchronized Archive get() {
        synchronized (mStatusLock) {
            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).
        if (mFailureException != null) {
        synchronized (mStatusLock) {
            if (mStatus == STATUS_FAILED) {
                throw new IllegalStateException(
                    "Trying to perform an operation on an archive which failed to load.",
                    mFailureException);
                        "Trying to perform an operation on an archive which failed to load.");
            }
        }

        try {
@@ -68,12 +82,15 @@ public class Loader {
                    mContext.getContentResolver().openFileDescriptor(
                            mArchiveUri, "r", null /* signal */),
                    mArchiveUri, mNotificationUri);
        } catch (IOException e) {
            mFailureException = e;
            throw new IllegalStateException(e);
        } catch (RuntimeException e) {
            mFailureException = e;
            throw e;
            synchronized (mStatusLock) {
                mStatus = STATUS_OPENED;
            }
        } catch (IOException | RuntimeException e) {
            Log.e(TAG, "Failed to open the archive.", e);
            synchronized (mStatusLock) {
                mStatus = STATUS_FAILED;
            }
            throw new IllegalStateException("Failed to open the archive.", e);
        } finally {
            // Notify observers that the root directory is loaded (or failed)
            // so clients reload it.
@@ -81,9 +98,16 @@ public class Loader {
                    ArchivesProvider.buildUriForArchive(mArchiveUri),
                    null /* observer */, false /* syncToNetwork */);
        }

        return mArchive;
    }

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

    Lock getReadLock() {
        return mLock.readLock();
    }
+3 −1
Original line number Diff line number Diff line
@@ -7,8 +7,10 @@ LOCAL_SRC_FILES := $(call all-java-files-under, common) \
    $(call all-java-files-under, unit) \
    $(call all-java-files-under, functional)

# For testing ZIP files.
# For testing ZIP files. Include testing ZIP files as uncompresseed raw
# resources.
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_AAPT_FLAGS += -0 .zip

LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ub-uiautomator espresso-core guava
+12 −0
Original line number Diff line number Diff line
@@ -29,6 +29,18 @@
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
       </provider>
       <!-- Provider for testing archives. -->
       <provider
            android:name="com.android.documentsui.archives.ResourcesProvider"
            android:authorities="com.android.documentsui.archives.resourcesprovider"
            android:exported="true"
            android:grantUriPermissions="true"
            android:permission="android.permission.MANAGE_DOCUMENTS"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
      </provider>
    </application>

    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
Loading