Add RoleManager into the system.

A role is a unique name within the system associated with certain
privileges. There can be multiple applications qualifying for a role,
but only a subset of them can become role holders. To qualify for a
role, an application must meet certain requirements, including
defining certain components in its manifest. Then the application will
need user consent to become the role holder.

Upon becoming a role holder, the application may be granted certain
privileges that are role specific. When an application loses its role,
these privileges will also be revoked.

Bug: 110557011
Test: build
Change-Id: Icd453a3b032857a8fd157048de8b9609f04e28b8
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
new file mode 100644
index 0000000..f7c6dea
--- /dev/null
+++ b/core/java/android/app/role/RoleManager.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2018 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.role;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+/**
+ * This class provides information about and manages roles.
+ * <p>
+ * A role is a unique name within the system associated with certain privileges. The list of
+ * available roles might change with a system app update, so apps should not make assumption about
+ * the availability of roles. Instead, they should always query if the role is available using
+ * {@link #isRoleAvailable(String)} before trying to do anything with it. Some predefined role names
+ * are available as constants in this class, and a list of possibly available roles can be found in
+ * the AndroidX Libraries.
+ * <p>
+ * There can be multiple applications qualifying for a role, but only a subset of them can become
+ * role holders. To qualify for a role, an application must meet certain requirements, including
+ * defining certain components in its manifest. These requirements can be found in the AndroidX
+ * Libraries. Then the application will need user consent to become a role holder, which can be
+ * requested using {@link Activity#startActivityForResult(Intent, int)} with the {@code Intent}
+ * obtained from {@link #createRequestRoleIntent(String)}.
+ * <p>
+ * Upon becoming a role holder, the application may be granted certain privileges that are role
+ * specific. When the application loses its role, these privileges will also be revoked.
+ */
+@SystemService(Context.ROLE_SERVICE)
+public final class RoleManager {
+
+    private static final String LOG_TAG = RoleManager.class.getSimpleName();
+
+    /**
+     * The name of the dialer role.
+     */
+    public static final String ROLE_DIALER = "android.app.role.DIALER";
+
+    /**
+     * The name of the SMS role.
+     */
+    public static final String ROLE_SMS = "android.app.role.SMS";
+
+    /**
+     * The action used to request user approval of a role for an application.
+     *
+     * @hide
+     */
+    public static final String ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE";
+
+    /**
+     * The name of the requested role.
+     * <p>
+     * <strong>Type:</strong> String
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME";
+
+    @NonNull
+    private final Context mContext;
+
+    @NonNull
+    private final IRoleManager mService;
+
+    /**
+     * @hide
+     */
+    public RoleManager(@NonNull Context context) throws ServiceManager.ServiceNotFoundException {
+        mContext = context;
+        mService = IRoleManager.Stub.asInterface(ServiceManager.getServiceOrThrow(
+                Context.ROLE_SERVICE));
+    }
+
+    /**
+     * Returns an {@code Intent} suitable for passing to {@link Activity#startActivityForResult(
+     * Intent, int)} which prompts the user to grant a role to this application.
+     * <p>
+     * If the role is granted, the {@code resultCode} will be {@link Activity#RESULT_OK}, otherwise
+     * it will be {@link Activity#RESULT_CANCELED}.
+     *
+     * @param roleName the name of requested role
+     *
+     * @return the {@code Intent} to prompt user to grant the role
+     *
+     * @throws IllegalArgumentException if {@code role} is {@code null} or empty
+     */
+    @NonNull
+    public Intent createRequestRoleIntent(@NonNull String roleName) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        Intent intent = new Intent(ACTION_REQUEST_ROLE);
+        intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName());
+        intent.putExtra(EXTRA_REQUEST_ROLE_NAME, roleName);
+        return intent;
+    }
+
+    /**
+     * Check whether a role is available in the system.
+     *
+     * @param roleName the name of role to checking for
+     *
+     * @return whether the role is available in the system
+     *
+     * @throws IllegalArgumentException if the role name is {@code null} or empty
+     */
+    public boolean isRoleAvailable(@NonNull String roleName) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        try {
+            return mService.isRoleAvailable(roleName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check whether the calling application is holding a particular role.
+     *
+     * @param roleName the name of the role to check for
+     *
+     * @return whether the calling application is holding the role
+     *
+     * @throws IllegalArgumentException if the role name is {@code null} or empty.
+     */
+    public boolean isRoleHeld(@NonNull String roleName) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        try {
+            return mService.isRoleHeld(roleName, mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get package names of the applications holding the role.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param roleName the name of the role to get the role holder for
+     * @param user the user to get the role holder for
+     *
+     * @return the package name of the role holder, or {@code null} if none.
+     *
+     * @throws IllegalArgumentException if the role name is {@code null} or empty.
+     *
+     * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+     *
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public Set<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        List<String> roleHolders;
+        try {
+            roleHolders = mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return new ArraySet<>(roleHolders);
+    }
+
+    /**
+     * Add a specific application to the holders of a role. If the role is exclusive, the previous
+     * holder will be replaced.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param roleName the name of the role to add the role holder for
+     * @param packageName the package name of the application to add to the role holders
+     * @param user the user to add the role holder for
+     * @param executor the {@code Executor} to run the callback on.
+     * @param callback the callback for whether this call is successful
+     *
+     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
+     *
+     * @see #getRoleHoldersAsUser(String, UserHandle)
+     * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+            @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
+            @NonNull RoleManagerCallback callback) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        try {
+            mService.addRoleHolderAsUser(roleName, packageName, user.getIdentifier(),
+                    new RoleManagerCallbackDelegate(executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove a specific application from the holders of a role.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param roleName the name of the role to remove the role holder for
+     * @param packageName the package name of the application to remove from the role holders
+     * @param user the user to remove the role holder for
+     * @param executor the {@code Executor} to run the callback on.
+     * @param callback the callback for whether this call is successful
+     *
+     * @throws IllegalArgumentException if the role name or package name is {@code null} or empty.
+     *
+     * @see #getRoleHoldersAsUser(String, UserHandle)
+     * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback)
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+            @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
+            @NonNull RoleManagerCallback callback) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        try {
+            mService.removeRoleHolderAsUser(roleName, packageName, user.getIdentifier(),
+                    new RoleManagerCallbackDelegate(executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove all holders of a role.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user
+     * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param roleName the name of the role to remove role holders for
+     * @param user the user to remove role holders for
+     * @param executor the {@code Executor} to run the callback on.
+     * @param callback the callback for whether this call is successful
+     *
+     * @throws IllegalArgumentException if the role name is {@code null} or empty.
+     *
+     * @see #getRoleHoldersAsUser(String, UserHandle)
+     * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback)
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+    @SystemApi
+    public void clearRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user,
+            @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) {
+        Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+        Preconditions.checkNotNull(user, "user cannot be null");
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        try {
+            mService.clearRoleHoldersAsUser(roleName, user.getIdentifier(),
+                    new RoleManagerCallbackDelegate(executor, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub {
+
+        @NonNull
+        private final Executor mExecutor;
+        @NonNull
+        private final RoleManagerCallback mCallback;
+
+        RoleManagerCallbackDelegate(@NonNull Executor executor,
+                @NonNull RoleManagerCallback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSuccess() {
+            long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(mCallback::onSuccess);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onFailure() {
+            long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(mCallback::onFailure);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+}