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);
+ }
+ }
+ }
+}