/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.provider;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider;
import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.io.FileNotFoundException;
import java.util.List;
/**
* Helper for accessing features in {@link DocumentsContract}.
*/
@SuppressWarnings("unused")
public final class DocumentsContractCompat {
/**
* Helper for accessing features in {@link DocumentsContract.Document}.
*/
public static final class DocumentCompat {
/**
* Flag indicating that a document is virtual, and doesn't have byte
* representation in the MIME type specified as {@link Document#COLUMN_MIME_TYPE}.
*
* <p><em>Virtual documents must have at least one alternative streamable
* format via {@link DocumentsProvider#openTypedDocument}</em>
*
* @see Document#FLAG_VIRTUAL_DOCUMENT
*/
public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9;
private DocumentCompat() {
}
}
private static final String PATH_TREE = "tree";
/**
* Checks if the given URI represents a {@link Document} backed by a
* {@link DocumentsProvider}.
*
* @see DocumentsContract#isDocumentUri(Context, Uri)
*/
public static boolean isDocumentUri(@NonNull Context context, @Nullable Uri uri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return DocumentsContractApi19Impl.isDocumentUri(context, uri);
}
return false;
}
/**
* Checks if the given URI represents a {@link Document} tree.
*
* @see DocumentsContract#isTreeUri(Uri)
*/
public static boolean isTreeUri(@NonNull Uri uri) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return false;
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
// While "tree" Uris were added in 21, the check was only (publicly) added in 24
final List<String> paths = uri.getPathSegments();
return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
} else {
return DocumentsContractApi24Impl.isTreeUri(uri);
}
}
/**
* Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
*
* @see DocumentsContract#getDocumentId(Uri)
*/
@Nullable
public static String getDocumentId(@NonNull Uri documentUri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return DocumentsContractApi19Impl.getDocumentId(documentUri);
}
return null;
}
/**
* Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI.
*
* @see DocumentsContract#getTreeDocumentId(Uri)
*/
@Nullable
public static String getTreeDocumentId(@NonNull Uri documentUri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.getTreeDocumentId(documentUri);
}
return null;
}
/**
* Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
* a document provider. When queried, a provider will return a single row
* with columns defined by {@link Document}.
*
* @see DocumentsContract#buildDocumentUri(String, String)
*/
@Nullable
public static Uri buildDocumentUri(@NonNull String authority, @NonNull String documentId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return DocumentsContractApi19Impl.buildDocumentUri(authority, documentId);
}
return null;
}
/**
* Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in
* a document provider. When queried, a provider will return a single row
* with columns defined by {@link Document}.
*/
@Nullable
public static Uri buildDocumentUriUsingTree(@NonNull Uri treeUri, @NonNull String documentId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.buildDocumentUriUsingTree(treeUri, documentId);
}
return null;
}
/**
* Build URI representing access to descendant documents of the given
* {@link Document#COLUMN_DOCUMENT_ID}.
*
* @see DocumentsContract#buildTreeDocumentUri(String, String)
*/
@Nullable
public static Uri buildTreeDocumentUri(@NonNull String authority, @NonNull String documentId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.buildTreeDocumentUri(authority, documentId);
}
return null;
}
/**
* Build URI representing the children of the target directory in a document
* provider. When queried, a provider will return zero or more rows with
* columns defined by {@link Document}.
*
* @see DocumentsContract#buildChildDocumentsUri(String, String)
*/
@Nullable
public static Uri buildChildDocumentsUri(@NonNull String authority,
@Nullable String parentDocumentId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.buildChildDocumentsUri(authority, parentDocumentId);
}
return null;
}
/**
* Build URI representing the children of the target directory in a document
* provider. When queried, a provider will return zero or more rows with
* columns defined by {@link Document}.
*
* @see DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)
*/
@Nullable
public static Uri buildChildDocumentsUriUsingTree(@NonNull Uri treeUri,
@NonNull String parentDocumentId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.buildChildDocumentsUriUsingTree(treeUri,
parentDocumentId);
}
return null;
}
/**
* Create a new document with given MIME type and display name.
*
* @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE}
* @param mimeType MIME type of new document
* @param displayName name of new document
* @return newly created document, or {@code null} if failed
*/
@Nullable
public static Uri createDocument(@NonNull ContentResolver content,
@NonNull Uri parentDocumentUri, @NonNull String mimeType, @NonNull String displayName)
throws FileNotFoundException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.createDocument(content, parentDocumentUri, mimeType,
displayName);
}
return null;
}
/**
* Change the display name of an existing document.
*
* @see DocumentsContract#renameDocument(ContentResolver, Uri, String)
*/
@Nullable
public static Uri renameDocument(@NonNull ContentResolver content,
@NonNull Uri documentUri, @NonNull String displayName) throws FileNotFoundException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return DocumentsContractApi21Impl.renameDocument(content, documentUri, displayName);
}
return null;
}
/**
* Removes the given document from a parent directory.
*
* In contrast to {@link DocumentsContract#deleteDocument} it requires specifying the parent.
* This method is especially useful if the document can be in multiple parents.
*
* This method was only added in {@link Build.VERSION_CODES#N}. On versions prior to this,
* this method calls through to {@link DocumentsContract#deleteDocument(ContentResolver, Uri)}.
*
* @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
* @param parentDocumentUri parent document of the document to remove.
* @return true if the document was removed successfully.
*/
public static boolean removeDocument(@NonNull ContentResolver content, @NonNull Uri documentUri,
@NonNull Uri parentDocumentUri) throws FileNotFoundException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return DocumentsContractApi24Impl.removeDocument(content, documentUri,
parentDocumentUri);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return DocumentsContractApi19Impl.deleteDocument(content, documentUri);
} else {
return false;
}
}
@RequiresApi(19)
private static class DocumentsContractApi19Impl {
@DoNotInline
public static Uri buildDocumentUri(String authority, String documentId) {
return DocumentsContract.buildDocumentUri(authority, documentId);
}
@DoNotInline
static boolean isDocumentUri(Context context, @Nullable Uri uri) {
return DocumentsContract.isDocumentUri(context, uri);
}
@DoNotInline
static String getDocumentId(Uri documentUri) {
return DocumentsContract.getDocumentId(documentUri);
}
@DoNotInline
static boolean deleteDocument(ContentResolver content, Uri documentUri)
throws FileNotFoundException {
return DocumentsContract.deleteDocument(content, documentUri);
}
private DocumentsContractApi19Impl() {
}
}
@RequiresApi(21)
private static class DocumentsContractApi21Impl {
@DoNotInline
static String getTreeDocumentId(Uri documentUri) {
return DocumentsContract.getTreeDocumentId(documentUri);
}
@DoNotInline
public static Uri buildTreeDocumentUri(String authority, String documentId) {
return DocumentsContract.buildTreeDocumentUri(authority, documentId);
}
@DoNotInline
static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId);
}
@DoNotInline
static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
return DocumentsContract.buildChildDocumentsUri(authority, parentDocumentId);
}
@DoNotInline
static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) {
return DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, parentDocumentId);
}
@DoNotInline
static Uri createDocument(ContentResolver content, Uri parentDocumentUri,
String mimeType, String displayName) throws FileNotFoundException {
return DocumentsContract.createDocument(content, parentDocumentUri, mimeType,
displayName);
}
@DoNotInline
static Uri renameDocument(@NonNull ContentResolver content,
@NonNull Uri documentUri, @NonNull String displayName)
throws FileNotFoundException {
return DocumentsContract.renameDocument(content, documentUri, displayName);
}
private DocumentsContractApi21Impl() {
}
}
@RequiresApi(24)
private static class DocumentsContractApi24Impl {
@DoNotInline
static boolean isTreeUri(@NonNull Uri uri) {
return DocumentsContract.isTreeUri(uri);
}
@DoNotInline
static boolean removeDocument(ContentResolver content, Uri documentUri,
Uri parentDocumentUri) throws FileNotFoundException {
return DocumentsContract.removeDocument(content, documentUri, parentDocumentUri);
}
private DocumentsContractApi24Impl() {
}
}
private DocumentsContractCompat() {
}
}