diff --git a/api/current.txt b/api/current.txt index 4e7d2dcd46829..889c05851ebef 100644 --- a/api/current.txt +++ b/api/current.txt @@ -32683,6 +32683,7 @@ package android.provider { method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); method public static java.util.List findDocumentPath(android.content.ContentResolver, android.net.Uri); method public static java.lang.String getDocumentId(android.net.Uri); @@ -32727,6 +32728,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 + field public static final int FLAG_WEB_LINKABLE = 4096; // 0x1000 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -32760,6 +32762,7 @@ package android.provider { ctor public DocumentsProvider(); method public java.lang.String copyDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; diff --git a/api/system-current.txt b/api/system-current.txt index 0acfea2453338..d9c9372c6109b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -35468,6 +35468,7 @@ package android.provider { method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); method public static java.util.List findDocumentPath(android.content.ContentResolver, android.net.Uri); method public static java.lang.String getDocumentId(android.net.Uri); @@ -35512,6 +35513,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 + field public static final int FLAG_WEB_LINKABLE = 4096; // 0x1000 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -35545,6 +35547,7 @@ package android.provider { ctor public DocumentsProvider(); method public java.lang.String copyDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; diff --git a/api/test-current.txt b/api/test-current.txt index 8abaf8490f347..7e84da391551a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -32797,6 +32797,7 @@ package android.provider { method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); method public static java.util.List findDocumentPath(android.content.ContentResolver, android.net.Uri); method public static java.lang.String getDocumentId(android.net.Uri); @@ -32841,6 +32842,7 @@ package android.provider { field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_VIRTUAL_DOCUMENT = 512; // 0x200 + field public static final int FLAG_WEB_LINKABLE = 4096; // 0x1000 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; } @@ -32874,6 +32876,7 @@ package android.provider { ctor public DocumentsProvider(); method public java.lang.String copyDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public android.content.IntentSender createWebLinkIntent(java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; method public android.provider.DocumentsContract.Path findDocumentPath(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index ded715f38426e..a6e6fda9980bd 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -28,6 +28,7 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; @@ -421,6 +422,14 @@ public final class DocumentsContract { */ public static final int FLAG_SUPPORTS_SETTINGS = 1 << 11; + /** + * Flag indicating that a Web link can be obtained for the document. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#createWebLinkIntent(PackageManager, Uri, Bundle) + */ + public static final int FLAG_WEB_LINKABLE = 1 << 12; + /** * Flag indicating that a document is not complete, likely its * contents are being downloaded. Partial files cannot be opened, @@ -685,12 +694,20 @@ public final class DocumentsContract { public static final String METHOD_EJECT_ROOT = "android:ejectRoot"; /** {@hide} */ public static final String METHOD_FIND_DOCUMENT_PATH = "android:findDocumentPath"; + /** {@hide} */ + public static final String METHOD_CREATE_WEB_LINK_INTENT = "android:createWebLinkIntent"; /** {@hide} */ public static final String EXTRA_PARENT_URI = "parentUri"; /** {@hide} */ public static final String EXTRA_URI = "uri"; + /** + * @see #createWebLinkIntent(ContentResolver, Uri, Bundle) + * {@hide} + */ + public static final String EXTRA_OPTIONS = "options"; + private static final String PATH_ROOT = "root"; private static final String PATH_RECENT = "recent"; private static final String PATH_DOCUMENT = "document"; @@ -1400,6 +1417,85 @@ public final class DocumentsContract { return out.getParcelable(DocumentsContract.EXTRA_RESULT); } + /** + * Creates an intent for obtaining a web link for the specified document. + * + *

Note, that due to internal limitations, if there is already a web link + * intent created for the specified document but with different options, + * then it may be overriden. + * + *

Providers are required to show confirmation UI for all new permissions granted + * for the linked document. + * + *

If list of recipients is known, then it should be passed in options as + * {@link Intent#EXTRA_EMAIL} as either a string or list of strings. Note, that + * this is just a hint for the provider, which can ignore the list. In either + * case the provider is required to show a UI for letting the user confirm + * any new permission grants. + * + *

Since this API may show a UI, it cannot be called from background. + * + *

In order to obtain the Web Link use code like this: + *


+     * void onSomethingHappened() {
+     *   IntentSender sender = DocumentsContract.createWebLinkIntent(...);
+     *   if (sender != null) {
+     *     startIntentSenderForResult(
+     *         DocumentsContract.createWebLinkIntent(...),
+     *         WEB_LINK_REQUEST_CODE,
+     *         null, 0, 0, 0, null);
+     *   }
+     * }
+     *
+     * (...)
+     *
+     * void onActivityResult(int requestCode, int resultCode, Intent data) {
+     *   if (requestCode == WEB_LINK_REQUEST_CODE && resultCode == RESULT_OK) {
+     *     Uri weblinkUri = data.getData();
+     *     ...
+     *   }
+     * }
+     * 
+ * + * @param uri uri for the document to create a link to. + * @param options Extra information for generating the link. + * @return an intent sender to obtain the web link, or null if the document + * is not linkable, or creating the intent sender failed. + * @see DocumentsProvider#createWebLinkIntent(String, Bundle) + * @see Intent#EXTRA_EMAIL + */ + public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri, + Bundle options) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + uri.getAuthority()); + try { + return createWebLinkIntent(client, uri, options); + } catch (Exception e) { + Log.w(TAG, "Failed to create a web link intent", e); + return null; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** + * {@hide} + */ + public static IntentSender createWebLinkIntent(ContentProviderClient client, Uri uri, + Bundle options) throws RemoteException { + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, uri); + + // Options may be provider specific, so put them in a separate bundle to + // avoid overriding the Uri. + if (options != null) { + in.putBundle(EXTRA_OPTIONS, options); + } + + final Bundle out = client.call(METHOD_CREATE_WEB_LINK_INTENT, 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 diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 8bc03ee622a2c..6170eb48444dd 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -18,6 +18,7 @@ package android.provider; import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; +import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_EJECT_ROOT; import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH; @@ -43,6 +44,7 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.IntentSender; import android.content.UriMatcher; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; @@ -362,6 +364,33 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("findDocumentPath not supported."); } + /** + * Creates an intent sender for a web link, if the document is web linkable. + *

+ * Before any new permissions are granted for the linked document, a visible + * UI must be shown, so the user can explicitly confirm whether the permission + * grants are expected. The user must be able to cancel the operation. + *

+ * Options passed as an argument may include a list of recipients, such + * as email addresses. The provider should reflect these options if possible, + * but it's acceptable to ignore them. In either case, confirmation UI must + * be shown before any new permission grants are granted. + *

+ * It is all right to generate a web link without granting new permissions, + * if opening the link would result in a page for requesting permission + * access. If it's impossible then the operation must fail by throwing an exception. + * + * @param documentId the document to create a web link intent for. + * @param options additional information, such as list of recipients. Optional. + * + * @see DocumentsContract.Document#FLAG_WEB_LINKABLE + * @see android.app.PendingIntent#getIntentSender + */ + public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options) + throws FileNotFoundException { + throw new UnsupportedOperationException("createWebLink is not supported."); + } + /** * 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 @@ -900,6 +929,14 @@ public abstract class DocumentsProvider extends ContentProvider { newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); + } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) { + enforceWritePermissionInner(documentUri, getCallingPackage(), null); + + final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS); + final IntentSender intentSender = createWebLinkIntent(documentId, options); + + out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender); + } else if (METHOD_RENAME_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri, getCallingPackage(), null);