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

Commit f06d0c5a authored by mrulhania's avatar mrulhania Committed by Manjeet Rulhania
Browse files

Add new permission API to optimize permission request flow

This CL keeps the API hidden. The short goal for this change
is to short circuit (performance optimization) permission
request if the permission is permanently denied.

We can also make the API public to help apps imorove their
permission request flow. Today, apps uses custom logic and
local state to infer permanent denial. This API can simplify
it, and will encourage apps not store permission state locally.

Bug: 378923900
Test: presubmit
FLAG: EXEMPT hidden api
Change-Id: Iaf333e28c45b9cb6299892891ba6355af9580024
parent a22952d7
Loading
Loading
Loading
Loading
+17 −4
Original line number Diff line number Diff line
@@ -2366,7 +2366,11 @@ class ContextImpl extends Context {
            Log.v(TAG, "Treating renounced permission " + permission + " as denied");
            return PERMISSION_DENIED;
        }
        int deviceId = resolveDeviceIdForPermissionCheck(permission);
        return PermissionManager.checkPermission(permission, pid, uid, deviceId);
    }

    private int resolveDeviceIdForPermissionCheck(String permission) {
        // When checking a device-aware permission on a remote device, if the permission is CAMERA
        // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
        // device doesn't have capability fall back to checking permission on the default device.
@@ -2400,8 +2404,7 @@ class ContextImpl extends Context {
                }
            }
        }

        return PermissionManager.checkPermission(permission, pid, uid, deviceId);
        return deviceId;
    }

    /** @hide */
@@ -2503,6 +2506,16 @@ class ContextImpl extends Context {
                message);
    }

    /** @hide */
    @Override
    public int getPermissionRequestState(String permission) {
        Objects.requireNonNull(permission, "Permission name can't be null");
        int deviceId = resolveDeviceIdForPermissionCheck(permission);
        PermissionManager permissionManager = getSystemService(PermissionManager.class);
        return permissionManager.getPermissionRequestState(getOpPackageName(), permission,
                deviceId);
    }

    @Override
    public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
         try {
+59 −0
Original line number Diff line number Diff line
@@ -787,6 +787,40 @@ public abstract class Context {
     */
    public static final int RECEIVER_NOT_EXPORTED = 0x4;

    /**
     * The permission is granted.
     *
     * @hide
     */
    public static final int PERMISSION_REQUEST_STATE_GRANTED = 0;

    /**
     * The permission isn't granted, but apps can request the permission. When the app request
     * the permission, user will be prompted with permission dialog to grant or deny the request.
     *
     * @hide
     */
    public static final int PERMISSION_REQUEST_STATE_REQUESTABLE = 1;

    /**
     * The permission is denied, and shouldn't be requested by apps. Permission request
     * will be automatically denied by the system, preventing the permission dialog from being
     * displayed to the user.
     *
     * @hide
     */
    public static final int PERMISSION_REQUEST_STATE_UNREQUESTABLE = 2;


    /** @hide */
    @IntDef(prefix = { "PERMISSION_REQUEST_STATE_" }, value = {
            PERMISSION_REQUEST_STATE_GRANTED,
            PERMISSION_REQUEST_STATE_REQUESTABLE,
            PERMISSION_REQUEST_STATE_UNREQUESTABLE
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface PermissionRequestState {}

    /**
     * Returns an AssetManager instance for the application's package.
     * <p>
@@ -6988,6 +7022,31 @@ public abstract class Context {
    public abstract void enforceCallingOrSelfPermission(
            @NonNull @PermissionName String permission, @Nullable String message);

    /**
     * Returns the permission request state for a given runtime permission. This method provides a
     * streamlined mechanism for applications to determine whether a permission can be
     * requested (i.e. whether the user will be prompted with a permission dialog).
     *
     * <p>Traditionally, determining if a permission has been permanently denied (unrequestable)
     * required applications to initiate a permission request and subsequently analyze the result
     * of {@link android.app.Activity#shouldShowRequestPermissionRationale} in conjunction with the
     * grant result within the {@link android.app.Activity#onRequestPermissionsResult} callback.
     *
     * @param permission The name of the permission.
     *
     * @return The current request state of the specified permission, represented by one of the
     * following constants: {@link PermissionRequestState#PERMISSION_REQUEST_STATE_GRANTED},
     * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_REQUESTABLE}, or
     * {@link PermissionRequestState#PERMISSION_REQUEST_STATE_UNREQUESTABLE}.
     *
     * @hide
     */
    @CheckResult
    @PermissionRequestState
    public int getPermissionRequestState(@NonNull String permission) {
        throw new RuntimeException("Not implemented. Must override in a subclass.");
    }

    /**
     * Grant permission to access a specific Uri to another package, regardless
     * of whether that package has general permission to access the Uri's
+6 −0
Original line number Diff line number Diff line
@@ -1012,6 +1012,12 @@ public class ContextWrapper extends Context {
        mBase.enforceCallingOrSelfPermission(permission, message);
    }

    /** @hide */
    @Override
    public int getPermissionRequestState(String permission) {
        return mBase.getPermissionRequestState(permission);
    }

    @Override
    public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
        mBase.grantUriPermission(toPackage, uri, modeFlags);
+2 −0
Original line number Diff line number Diff line
@@ -108,6 +108,8 @@ interface IPermissionManager {
    int checkUidPermission(int uid, String permissionName, int deviceId);

    Map<String, PermissionState> getAllPermissionStates(String packageName, String persistentDeviceId, int userId);

    int getPermissionRequestState(String packageName, String permissionName, int deviceId);
}

/**
+70 −0
Original line number Diff line number Diff line
@@ -1742,6 +1742,16 @@ public final class PermissionManager {
        }
    }

    private static int getPermissionRequestStateUncached(String packageName, String permission,
            int deviceId) {
        try {
            return AppGlobals.getPermissionManager().getPermissionRequestState(
                    packageName, permission, deviceId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Identifies a permission query.
     *
@@ -1795,6 +1805,46 @@ public final class PermissionManager {
        }
    }

    private static final class PermissionRequestStateQuery {
        final String mPackageName;
        final String mPermission;
        final int mDeviceId;

        PermissionRequestStateQuery(@NonNull String packageName, @NonNull String permission,
                int deviceId) {
            mPackageName = packageName;
            mPermission = permission;
            mDeviceId = deviceId;
        }

        @Override
        public String toString() {
            return TextUtils.formatSimple("PermissionRequestStateQuery(package=\"%s\","
                            + " permission=\"%s\", " + "deviceId=%d)",
                    mPackageName, mPermission, mDeviceId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(mPackageName, mPermission, mDeviceId);
        }

        @Override
        public boolean equals(@Nullable Object rval) {
            if (rval == null) {
                return false;
            }
            PermissionRequestStateQuery other;
            try {
                other = (PermissionRequestStateQuery) rval;
            } catch (ClassCastException ex) {
                return false;
            }
            return mDeviceId == other.mDeviceId && Objects.equals(mPackageName, other.mPackageName)
                    && Objects.equals(mPermission, other.mPermission);
        }
    }

    // The legacy system property "package_info" had two purposes: to invalidate PIC caches and to
    // signal that package information, and therefore permissions, might have changed.
    // AudioSystem is the only client of the signaling behavior.  The "separate permissions
@@ -1841,11 +1891,31 @@ public final class PermissionManager {
                }
            };

    /** @hide */
    private static final PropertyInvalidatedCache<PermissionRequestStateQuery, Integer>
            sPermissionRequestStateCache =
            new PropertyInvalidatedCache<>(
                    512, CACHE_KEY_PACKAGE_INFO_CACHE, "getPermissionRequestState") {
                @Override
                public Integer recompute(PermissionRequestStateQuery query) {
                    return getPermissionRequestStateUncached(query.mPackageName, query.mPermission,
                            query.mDeviceId);
                }
            };

    /** @hide */
    public static int checkPermission(@Nullable String permission, int pid, int uid, int deviceId) {
        return sPermissionCache.query(new PermissionQuery(permission, pid, uid, deviceId));
    }

    /** @hide */
    @Context.PermissionRequestState
    public int getPermissionRequestState(@NonNull String packageName, @NonNull String permission,
            int deviceId) {
        return sPermissionRequestStateCache.query(
                new PermissionRequestStateQuery(packageName, permission, deviceId));
    }

    /**
     * Gets the permission states for requested package and persistent device.
     * <p>
Loading