diff --git a/api/current.txt b/api/current.txt index f31593dd79194..707329ccc1044 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3513,6 +3513,7 @@ package android.app { method public deprecated void setTitleColor(int); method public void setVisible(boolean); method public final void setVolumeControlStream(int); + method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public boolean shouldUpRecreateTask(android.content.Intent); method public final deprecated void showDialog(int); method public final deprecated boolean showDialog(int, android.os.Bundle); diff --git a/api/system-current.txt b/api/system-current.txt index cf6d0f6fe5495..9e99562d723ca 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3599,6 +3599,7 @@ package android.app { method public deprecated void setTitleColor(int); method public void setVisible(boolean); method public final void setVolumeControlStream(int); + method public boolean shouldShowRequestPermissionRationale(java.lang.String); method public boolean shouldUpRecreateTask(android.content.Intent); method public final deprecated void showDialog(int); method public final deprecated boolean showDialog(int, android.os.Bundle); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 49f509974fcd0..a8eaaae16ad2d 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3746,6 +3746,7 @@ public class Activity extends ContextThemeWrapper * * @see #onRequestPermissionsResult(int, String[], int[]) * @see #checkSelfPermission(String) + * @see #canShowRequestPermissionRationale(String) */ public final void requestPermissions(@NonNull String[] permissions, int requestCode) { Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions); @@ -3769,6 +3770,30 @@ public class Activity extends ContextThemeWrapper /* callback - no nothing */ } + /** + * Gets whether you should show UI with rationale for requesting a permission. + * You should do this only if you do not have the permission and the context in + * which the permission is requested does not clearly communicate to the user + * what would be the benefit from granting this permission. + *

+ * For example, if you write a camera app, requesting the camera permission + * would be expected by the user and no rationale for why it is requested is + * needed. If however, the app needs location for tagging photos then a non-tech + * savvy user may wonder how location is related to taking photos. In this case + * you may choose to show UI with rationale of requesting this permission. + *

+ * + * @param permission A permission your app wants to request. + * @return Whether you can show permission rationale UI. + * + * @see #checkSelfPermission(String) + * @see #requestPermissions(String[], int) + * @see #onRequestPermissionsResult(int, String[], int[]) + */ + public boolean shouldShowRequestPermissionRationale(String permission) { + return getPackageManager().shouldShowRequestPermissionRationale(permission); + } + /** * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} * with no options. diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 04f643074592e..41e3db834f483 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -485,6 +485,16 @@ final class ApplicationPackageManager extends PackageManager { } } + @Override + public boolean shouldShowRequestPermissionRationale(String permission) { + try { + return mPM.shouldShowRequestPermissionRationale(permission, + mContext.getPackageName(), mContext.getUserId()); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + @Override public int checkSignatures(String pkg1, String pkg2) { try { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index ddff7821cdcb9..00b8c71fde32c 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -105,6 +105,9 @@ interface IPackageManager { void updatePermissionFlags(String permissionName, String packageName, int flagMask, int flagValues, int userId); + boolean shouldShowRequestPermissionRationale(String permissionName, + String packageName, int userId); + boolean isProtectedBroadcast(String actionName); int checkSignatures(String pkg1, String pkg2); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 2ca030635ef53..45245e4af95d5 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2538,6 +2538,19 @@ public abstract class PackageManager { String packageName, @PermissionFlags int flagMask, int flagValues, @NonNull UserHandle user); + /** + * Gets whether you should show UI with rationale for requesting a permission. + * You should do this only if you do not have the permission and the context in + * which the permission is requested does not clearly communicate to the user + * what would be the benefit from grating this permission. + * + * @param permission A permission your app wants to request. + * @return Whether you can show permission rationale UI. + * + * @hide + */ + public abstract boolean shouldShowRequestPermissionRationale(String permission); + /** * Returns an {@link android.content.Intent} suitable for passing to * {@link android.app.Activity#startActivityForResult(android.content.Intent, int)} diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 9b619c79749de..47e39631cfca2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3359,6 +3359,46 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override + public boolean shouldShowRequestPermissionRationale(String permissionName, + String packageName, int userId) { + if (UserHandle.getCallingUserId() != userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "canShowRequestPermissionRationale for user " + userId); + } + + final int uid = getPackageUid(packageName, userId); + if (UserHandle.getAppId(getCallingUid()) != UserHandle.getAppId(uid)) { + return false; + } + + if (checkPermission(permissionName, packageName, userId) + == PackageManager.PERMISSION_GRANTED) { + return false; + } + + final int flags; + + final long identity = Binder.clearCallingIdentity(); + try { + flags = getPermissionFlags(permissionName, + packageName, userId); + } finally { + Binder.restoreCallingIdentity(identity); + } + + final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED + | PackageManager.FLAG_PERMISSION_POLICY_FIXED + | PackageManager.FLAG_PERMISSION_USER_FIXED; + + if ((flags & fixedFlags) != 0) { + return false; + } + + return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; + } + @Override public boolean isProtectedBroadcast(String actionName) { synchronized (mPackages) { diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index bf1ea4d0d4e92..3b7aa9f3fecb2 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -218,6 +218,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public boolean shouldShowRequestPermissionRationale(String permission) { + throw new UnsupportedOperationException(); + } + @Override public int checkSignatures(String pkg1, String pkg2) { throw new UnsupportedOperationException();