From df6d37e50442cb453a7e0d9e383c45bba848db70 Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Mon, 20 Mar 2017 18:51:20 -0700 Subject: [PATCH] Introduce AuthenticationRequiredException, and @hide RecoverableSecurityException. Test: CTS tests in changes in same topic. Bug: 36482356 Bug: 36482258 Change-Id: I44b3407746006d8709e4a3f3ca2950c61fa0be95 --- api/current.txt | 19 ++- api/system-current.txt | 19 ++- api/test-current.txt | 19 ++- .../app/AuthenticationRequiredException.java | 99 +++++++++++++++ .../app/RecoverableSecurityException.java | 3 +- .../android/provider/DocumentsProvider.java | 120 ++++++++---------- 6 files changed, 177 insertions(+), 102 deletions(-) create mode 100644 core/java/android/app/AuthenticationRequiredException.java diff --git a/api/current.txt b/api/current.txt index 235a4a6c3d4d3..90c243765e351 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4293,6 +4293,14 @@ package android.app { field public java.lang.String serviceDetails; } + public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class AutomaticZenRule implements android.os.Parcelable { ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean); ctor public AutomaticZenRule(android.os.Parcel); @@ -5700,17 +5708,6 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } - public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); - method public int describeContents(); - method public android.app.RemoteAction getUserAction(); - method public java.lang.CharSequence getUserMessage(); - method public void showAsDialog(android.app.Activity); - method public void showAsNotification(android.content.Context, java.lang.String); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public android.app.RemoteAction clone(); diff --git a/api/system-current.txt b/api/system-current.txt index abdcaaa6c1b42..8b97f66c7323a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4436,6 +4436,14 @@ package android.app { field public java.lang.String serviceDetails; } + public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class AutomaticZenRule implements android.os.Parcelable { ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean); ctor public AutomaticZenRule(android.os.Parcel); @@ -5901,17 +5909,6 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } - public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); - method public int describeContents(); - method public android.app.RemoteAction getUserAction(); - method public java.lang.CharSequence getUserMessage(); - method public void showAsDialog(android.app.Activity); - method public void showAsNotification(android.content.Context, java.lang.String); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public android.app.RemoteAction clone(); diff --git a/api/test-current.txt b/api/test-current.txt index 3fb42a400835b..dfff54638e234 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4303,6 +4303,14 @@ package android.app { field public java.lang.String serviceDetails; } + public final class AuthenticationRequiredException extends java.lang.SecurityException implements android.os.Parcelable { + ctor public AuthenticationRequiredException(java.lang.Throwable, android.app.PendingIntent); + method public int describeContents(); + method public android.app.PendingIntent getUserAction(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + public final class AutomaticZenRule implements android.os.Parcelable { ctor public AutomaticZenRule(java.lang.String, android.content.ComponentName, android.net.Uri, int, boolean); ctor public AutomaticZenRule(android.os.Parcel); @@ -5711,17 +5719,6 @@ package android.app { field public static final int STYLE_SPINNER = 0; // 0x0 } - public final class RecoverableSecurityException extends java.lang.SecurityException implements android.os.Parcelable { - ctor public RecoverableSecurityException(java.lang.Throwable, java.lang.CharSequence, android.app.RemoteAction); - method public int describeContents(); - method public android.app.RemoteAction getUserAction(); - method public java.lang.CharSequence getUserMessage(); - method public void showAsDialog(android.app.Activity); - method public void showAsNotification(android.content.Context, java.lang.String); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - public final class RemoteAction implements android.os.Parcelable { ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.PendingIntent); method public android.app.RemoteAction clone(); diff --git a/core/java/android/app/AuthenticationRequiredException.java b/core/java/android/app/AuthenticationRequiredException.java new file mode 100644 index 0000000000000..89609794a615d --- /dev/null +++ b/core/java/android/app/AuthenticationRequiredException.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 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 android.app; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Specialization of {@link SecurityException} that is thrown when authentication is needed from the + * end user before viewing the content. + *

+ * This exception is only appropriate where there is a concrete action the user can take to + * authorize and make forward progress, such as confirming or entering authentication credentials, + * or granting access via other means. + *

+ * Note: legacy code that receives this exception may treat it as a general + * {@link SecurityException}, and thus there is no guarantee that the action contained will be + * invoked by the user. + *

+ */ +public final class AuthenticationRequiredException extends SecurityException implements Parcelable { + private static final String TAG = "AuthenticationRequiredException"; + + private final PendingIntent mUserAction; + + /** {@hide} */ + public AuthenticationRequiredException(Parcel in) { + this(new SecurityException(in.readString()), PendingIntent.CREATOR.createFromParcel(in)); + } + + /** + * Create an instance ready to be thrown. + * + * @param cause original cause with details designed for engineering + * audiences. + * @param userAction primary action that will initiate the recovery. This + * must launch an activity that is expected to set + * {@link Activity#setResult(int)} before finishing to + * communicate the final status of the recovery. For example, + * apps that observe {@link Activity#RESULT_OK} may choose to + * immediately retry their operation. If this exception was + * thrown from a {@link ContentProvider}, you should also send + * any relevant {@link ContentResolver#notifyChange} events to + * trigger reloading of data. + */ + public AuthenticationRequiredException(Throwable cause, PendingIntent userAction) { + super(cause.getMessage()); + mUserAction = Preconditions.checkNotNull(userAction); + } + + /** + * Return primary action that will initiate the authorization. + */ + public PendingIntent getUserAction() { + return mUserAction; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getMessage()); + mUserAction.writeToParcel(dest, flags); + } + + public static final Creator CREATOR = + new Creator() { + @Override + public AuthenticationRequiredException createFromParcel(Parcel source) { + return new AuthenticationRequiredException(source); + } + + @Override + public AuthenticationRequiredException[] newArray(int size) { + return new AuthenticationRequiredException[size]; + } + }; +} diff --git a/core/java/android/app/RecoverableSecurityException.java b/core/java/android/app/RecoverableSecurityException.java index 8612f186ade42..a503a46a29dc1 100644 --- a/core/java/android/app/RecoverableSecurityException.java +++ b/core/java/android/app/RecoverableSecurityException.java @@ -45,7 +45,8 @@ import com.android.internal.util.Preconditions; * Note: legacy code that receives this exception may treat it as a general * {@link SecurityException}, and thus there is no guarantee that the messages * contained will be shown to the end user. - *

+ * + * @hide */ public final class RecoverableSecurityException extends SecurityException implements Parcelable { private static final String TAG = "RecoverableSecurityException"; diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 620d33a5e9156..9e68afbb52978 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -38,7 +38,7 @@ import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.annotation.Nullable; -import android.app.RecoverableSecurityException; +import android.app.AuthenticationRequiredException; import android.content.ClipDescription; import android.content.ContentProvider; import android.content.ContentResolver; @@ -235,10 +235,6 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must * not change once returned. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param parentDocumentId the parent directory to create the new document * under. @@ -247,6 +243,10 @@ public abstract class DocumentsProvider extends ContentProvider { * @param displayName the display name of the new document. The provider may * alter this name to meet any internal constraints, such as * avoiding conflicting names. + + * @throws AuthenticationRequiredException If authentication is required from the user (such as + * login credentials), but it is not guaranteed that the client will handle this + * properly. */ @SuppressWarnings("unused") public String createDocument(String parentDocumentId, String mimeType, String displayName) @@ -262,15 +262,14 @@ public abstract class DocumentsProvider extends ContentProvider { * URI permission grants will be updated to point at the new document. If * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the * rename, return {@code null}. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param documentId the document to rename. * @param displayName the updated display name of the document. The provider * may alter this name to meet any internal constraints, such as * avoiding conflicting names. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ @SuppressWarnings("unused") public String renameDocument(String documentId, String displayName) @@ -286,12 +285,11 @@ public abstract class DocumentsProvider extends ContentProvider { * call (such as documents inside a directory) the implementor is * responsible for revoking those permissions using * {@link #revokeDocumentPermission(String)}. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param documentId the document to delete. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ @SuppressWarnings("unused") public void deleteDocument(String documentId) throws FileNotFoundException { @@ -305,13 +303,12 @@ public abstract class DocumentsProvider extends ContentProvider { * the same document provider. Upon completion returns the document id of * the copied document at the target destination. {@code null} must never * be returned. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param sourceDocumentId the document to copy. * @param targetParentDocumentId the target document to be copied into as a child. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ @SuppressWarnings("unused") public String copyDocument(String sourceDocumentId, String targetParentDocumentId) @@ -329,15 +326,14 @@ public abstract class DocumentsProvider extends ContentProvider { * *

It's the responsibility of the provider to revoke grants if the document * is no longer accessible using sourceDocumentId. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param sourceDocumentId the document to move. * @param sourceParentDocumentId the parent of the document to move. * @param targetParentDocumentId the target document to be a new parent of the * source document. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ @SuppressWarnings("unused") public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, @@ -355,11 +351,11 @@ public abstract class DocumentsProvider extends ContentProvider { *

It's the responsibility of the provider to revoke grants if the document is * removed from the last parent, and effectively the document is deleted. * - *

{@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * @param documentId the document to remove. * @param parentDocumentId the parent of the document to move. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ @SuppressWarnings("unused") public void removeDocument(String documentId, String parentDocumentId) @@ -377,9 +373,6 @@ public abstract class DocumentsProvider extends ContentProvider { *

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. - *

{@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly.perly. * * * @param parentDocumentId the document from which the path starts if not null, @@ -388,6 +381,9 @@ public abstract class DocumentsProvider extends ContentProvider { * @return the path of the requested document. If parentDocumentId is null * returned root ID must not be null. If parentDocumentId is not null * returned root ID must be null. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException { @@ -397,7 +393,7 @@ public abstract class DocumentsProvider extends ContentProvider { /** * Creates an intent sender for a web link, if the document is web linkable. *

- * {@link RecoverableSecurityException} can be thrown if user does not have + * {@link AuthenticationRequiredException} can be thrown if user does not have * sufficient permission for the linked document. 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. @@ -414,6 +410,9 @@ public abstract class DocumentsProvider extends ContentProvider { * * @param documentId the document to create a web link intent for. * @param options additional information, such as list of recipients. Optional. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * * @see DocumentsContract.Document#FLAG_WEB_LINKABLE * @see android.app.PendingIntent#getIntentSender @@ -436,9 +435,6 @@ public abstract class DocumentsProvider extends ContentProvider { * android.database.ContentObserver, boolean)} with * {@link DocumentsContract#buildRootsUri(String)} to notify the system. *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param projection list of {@link Root} columns to put into the cursor. If * {@code null} all supported columns should be included. @@ -452,10 +448,6 @@ public abstract class DocumentsProvider extends ContentProvider { * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and * limited to only return the 64 most recently modified documents. *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. - *

* Recent documents do not support change notifications. * * @param projection list of {@link Document} columns to put into the @@ -472,16 +464,14 @@ public abstract class DocumentsProvider extends ContentProvider { /** * Return metadata for the single requested document. You should avoid * making network requests to keep this request fast. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. - * * * @param documentId the document to return. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ public abstract Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException; @@ -509,11 +499,6 @@ public abstract class DocumentsProvider extends ContentProvider { * you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. - * * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the @@ -525,6 +510,9 @@ public abstract class DocumentsProvider extends ContentProvider { * may be unordered. This ordering is a hint that can be used to * prioritize how data is fetched from the network, but UI may * always enforce a specific ordering. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * @see DocumentsContract#EXTRA_LOADING * @see DocumentsContract#EXTRA_INFO * @see DocumentsContract#EXTRA_ERROR @@ -552,10 +540,6 @@ public abstract class DocumentsProvider extends ContentProvider { * you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the @@ -567,6 +551,9 @@ public abstract class DocumentsProvider extends ContentProvider { * will be used, which may be unordered. See * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for * details. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * * @see DocumentsContract#EXTRA_LOADING * @see DocumentsContract#EXTRA_INFO @@ -609,16 +596,16 @@ public abstract class DocumentsProvider extends ContentProvider { * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), or returned as part of - * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param rootId the root to search under. * @param query string to match documents against. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. + * * @see DocumentsContract#EXTRA_LOADING * @see DocumentsContract#EXTRA_INFO * @see DocumentsContract#EXTRA_ERROR @@ -641,9 +628,9 @@ public abstract class DocumentsProvider extends ContentProvider { * implementation queries {@link #queryDocument(String, String[])}, so * providers may choose to override this as an optimization. *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. */ public String getDocumentType(String documentId) throws FileNotFoundException { final Cursor cursor = queryDocument(documentId, null); @@ -669,15 +656,14 @@ public abstract class DocumentsProvider extends ContentProvider { *

* If you block while downloading content, you should periodically check * {@link CancellationSignal#isCanceled()} to abort abandoned open requests. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param documentId the document to return. * @param mode the mode to open with, such as 'r', 'w', or 'rw'. * @param signal used by the caller to signal if the request should be * cancelled. May be null. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, * OnCloseListener) * @see ParcelFileDescriptor#createReliablePipe() @@ -697,15 +683,14 @@ public abstract class DocumentsProvider extends ContentProvider { * If you perform expensive operations to download or generate a thumbnail, * you should periodically check {@link CancellationSignal#isCanceled()} to * abort abandoned thumbnail requests. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param documentId the document to return. * @param sizeHint hint of the optimal thumbnail dimensions. * @param signal used by the caller to signal if the request should be * cancelled. May be null. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * @see Document#FLAG_SUPPORTS_THUMBNAIL */ @SuppressWarnings("unused") @@ -723,10 +708,6 @@ public abstract class DocumentsProvider extends ContentProvider { * matching the specified MIME type filter. *

* Virtual documents must have at least one streamable format. - *

- * {@link RecoverableSecurityException} can be thrown if more input is required - * from the user (such as insufficient permission), but it is not guaranteed that - * the client will handle this properly. * * @param documentId the document to return. * @param mimeTypeFilter the MIME type filter for the requested format. May @@ -735,6 +716,9 @@ public abstract class DocumentsProvider extends ContentProvider { * provider. * @param signal used by the caller to signal if the request should be * cancelled. May be null. + * @throws AuthenticationRequiredException If authentication is required from + * the user (such as login credentials), but it is not guaranteed + * that the client will handle this properly. * @see #getDocumentStreamTypes(String, String) */ @SuppressWarnings("unused")