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

Commit 2f6d0d6d authored by Garfield Tan's avatar Garfield Tan
Browse files

DO NOT MERGE ANYWHERE: Add findPath API to SAF.

Implement it in ExternalStorageProvider.

Bug: 30948740
Change-Id: I03241cdfa561ef2fc0a0b829c9a59ad845e8f844
(cherry picked from commit 51efc73f3f341393cf93f71604be791205021b69)
parent 12e319b7
Loading
Loading
Loading
Loading
+86 −0
Original line number Diff line number Diff line
@@ -36,8 +36,10 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.storage.StorageVolume;
import android.system.ErrnoException;
@@ -644,6 +646,8 @@ public final class DocumentsContract {
    public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument";
    /** {@hide} */
    public static final String METHOD_EJECT_ROOT = "android:ejectRoot";
    /** {@hide} */
    public static final String METHOD_FIND_PATH = "android:findPath";

    /** {@hide} */
    public static final String EXTRA_PARENT_URI = "parentUri";
@@ -1306,6 +1310,41 @@ public final class DocumentsContract {
        return out.getBoolean(DocumentsContract.EXTRA_RESULT);
    }

    /**
     * Finds the canonical path to the root. Document id should be unique across
     * roots.
     *
     * @param documentUri uri of the document which path is requested.
     * @return the path to the root of the document, or {@code null} if failed.
     * @see DocumentsProvider#findPath(String)
     *
     * {@hide}
     */
    public static Path findPath(ContentResolver resolver, Uri documentUri)
            throws RemoteException {
        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                documentUri.getAuthority());
        try {
            return findPath(client, documentUri);
        } catch (Exception e) {
            Log.w(TAG, "Failed to find path", e);
            return null;
        } finally {
            ContentProviderClient.releaseQuietly(client);
        }
    }

    /** {@hide} */
    public static Path findPath(ContentProviderClient client, Uri documentUri)
            throws RemoteException {
        final Bundle in = new Bundle();
        in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);

        final Bundle out = client.call(METHOD_FIND_PATH, null, in);

        return out.getParcelable(DocumentsContract.EXTRA_RESULT);
    }

    /**
     * Open the given image for thumbnail purposes, using any embedded EXIF
     * thumbnail if available, and providing orientation hints from the parent
@@ -1345,4 +1384,51 @@ public final class DocumentsContract {

        return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
    }

    /**
     * Holds a path from a root to a particular document under it.
     *
     * @hide
     */
    public static final class Path implements Parcelable {

        public final String mRootId;
        public final List<String> mPath;

        /**
         * Creates a Path.
         * @param rootId the id of the root
         * @param path the list of document ids from the root document
         *             at position 0 to the target document
         */
        public Path(String rootId, List<String> path) {
            mRootId = rootId;
            mPath = path;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(mRootId);
            dest.writeStringList(mPath);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public static final Creator<Path> CREATOR = new Creator<Path>() {
            @Override
            public Path createFromParcel(Parcel in) {
                final String rootId = in.readString();
                final List<String> path = in.createStringArrayList();
                return new Path(rootId, path);
            }

            @Override
            public Path[] newArray(int size) {
                return new Path[size];
            }
        };
    }
}
+29 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
import static android.provider.DocumentsContract.METHOD_FIND_PATH;
import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
@@ -33,6 +34,7 @@ import static android.provider.DocumentsContract.getSearchDocumentsQuery;
import static android.provider.DocumentsContract.getTreeDocumentId;
import static android.provider.DocumentsContract.isTreeUri;

import android.Manifest;
import android.annotation.CallSuper;
import android.content.ClipDescription;
import android.content.ContentProvider;
@@ -53,6 +55,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract.Path;
import android.util.Log;

import libcore.io.IoUtils;
@@ -322,6 +325,26 @@ public abstract class DocumentsProvider extends ContentProvider {
        throw new UnsupportedOperationException("Remove not supported");
    }

    /**
     * Finds the canonical path to the root for the requested document. If there are
     * more than one path to this document, return the most typical one.
     *
     * <p>This API assumes that document id has enough info to infer the root.
     * Different roots should use different document id to refer to the same
     * document.
     *
     * @param documentId the document which path is requested.
     * @return the path of the requested document to the root, or null if
     *      such operation is not supported.
     *
     * @hide
     */
    public Path findPath(String documentId)
            throws FileNotFoundException {
        Log.w(TAG, "findPath is called on an unsupported provider.");
        return null;
    }

    /**
     * Return all roots currently provided. To display to users, you must define
     * at least one root. You should avoid making network requests to keep this
@@ -873,6 +896,12 @@ public abstract class DocumentsProvider extends ContentProvider {

            // It's responsibility of the provider to revoke any grants, as the document may be
            // still attached to another parents.
        } else if (METHOD_FIND_PATH.equals(method)) {
            getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);

            final Path path = findPath(documentId);

            out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
        } else {
            throw new UnsupportedOperationException("Method not supported " + method);
        }
+32 −3
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ import android.os.storage.VolumeInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract.Path;
import android.provider.DocumentsProvider;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -48,6 +49,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.Log;
import android.util.Pair;
import android.webkit.MimeTypeMap;

import com.android.internal.annotations.GuardedBy;
@@ -183,7 +185,8 @@ public class ExternalStorageProvider extends DocumentsProvider {
            root.rootId = rootId;
            root.volumeId = volume.id;
            root.flags = Root.FLAG_LOCAL_ONLY
                    | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
                    | Root.FLAG_SUPPORTS_SEARCH
                    | Root.FLAG_SUPPORTS_IS_CHILD;

            final DiskInfo disk = volume.getDisk();
            if (DEBUG) Log.d(TAG, "Disk for root " + rootId + " is " + disk);
@@ -270,7 +273,6 @@ public class ExternalStorageProvider extends DocumentsProvider {
        return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
    }


    private String getDocIdForFile(File file) throws FileNotFoundException {
        return getDocIdForFileMaybeCreate(file, false);
    }
@@ -323,6 +325,11 @@ public class ExternalStorageProvider extends DocumentsProvider {
    }

    private File getFileForDocId(String docId, boolean visible) throws FileNotFoundException {
        return resolveDocId(docId, visible).second;
    }

    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible)
            throws FileNotFoundException {
        final int splitIndex = docId.indexOf(':', 1);
        final String tag = docId.substring(0, splitIndex);
        final String path = docId.substring(splitIndex + 1);
@@ -346,7 +353,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
        if (!target.exists()) {
            throw new FileNotFoundException("Missing file for " + docId + " at " + target);
        }
        return target;
        return Pair.create(root, target);
    }

    private void includeFile(MatrixCursor result, String docId, File file)
@@ -422,6 +429,28 @@ public class ExternalStorageProvider extends DocumentsProvider {
        }
    }

    @Override
    public Path findPath(String documentId)
            throws FileNotFoundException {
        LinkedList<String> path = new LinkedList<>();

        final Pair<RootInfo, File> resolvedDocId = resolveDocId(documentId, false);
        RootInfo root = resolvedDocId.first;
        File file = resolvedDocId.second;

        if (!file.exists()) {
            throw new FileNotFoundException();
        }

        while (file != null && file.getAbsolutePath().startsWith(root.path.getAbsolutePath())) {
            path.addFirst(getDocIdForFile(file));

            file = file.getParentFile();
        }

        return new Path(root.rootId, path);
    }

    @Override
    public String createDocument(String docId, String mimeType, String displayName)
            throws FileNotFoundException {