summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp6
-rw-r--r--apex_manifest.json2
-rw-r--r--framework-s/Android.bp72
-rw-r--r--framework-s/api/current.txt19
-rw-r--r--framework-s/api/module-lib-current.txt11
-rw-r--r--framework-s/api/module-lib-removed.txt1
-rw-r--r--framework-s/api/removed.txt1
-rw-r--r--framework-s/api/system-current.txt43
-rw-r--r--framework-s/api/system-removed.txt1
-rw-r--r--framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl25
-rw-r--r--framework-s/java/android/app/role/IRoleController.aidl43
-rw-r--r--framework-s/java/android/app/role/IRoleManager.aidl61
-rw-r--r--framework-s/java/android/app/role/OnRoleHoldersChangedListener.java38
-rw-r--r--framework-s/java/android/app/role/RoleControllerManager.java265
-rw-r--r--framework-s/java/android/app/role/RoleControllerService.java304
-rw-r--r--framework-s/java/android/app/role/RoleFrameworkInitializer.java44
-rw-r--r--framework-s/java/android/app/role/RoleManager.java773
-rw-r--r--framework-s/java/android/app/role/TEST_MAPPING12
-rw-r--r--framework/Android.bp2
-rw-r--r--jarjar-rules.txt5
-rw-r--r--service/Android.bp61
-rw-r--r--service/api/system-server-current.txt8
-rw-r--r--service/java/com/android/permission/compat/UserHandleCompat.java48
-rw-r--r--service/java/com/android/permission/compat/package-info.java22
-rw-r--r--service/java/com/android/permission/util/ArrayUtils.java70
-rw-r--r--service/java/com/android/permission/util/BackgroundThread.java93
-rw-r--r--service/java/com/android/permission/util/CollectionUtils.java72
-rw-r--r--service/java/com/android/permission/util/ForegroundThread.java93
-rw-r--r--service/java/com/android/permission/util/ThrottledRunnable.java75
-rw-r--r--service/java/com/android/permission/util/package-info.java22
-rw-r--r--service/java/com/android/role/RoleManagerLocal.java42
-rw-r--r--service/java/com/android/role/RoleService.java734
-rw-r--r--service/java/com/android/role/RoleShellCommand.java151
-rw-r--r--service/java/com/android/role/RoleUserState.java476
-rw-r--r--service/java/com/android/role/TEST_MAPPING20
-rw-r--r--service/java/com/android/role/package-info.java22
-rw-r--r--service/proto/com/android/role/roleservice.proto56
37 files changed, 3792 insertions, 1 deletions
diff --git a/Android.bp b/Android.bp
index e30df05b2..be51143e4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -26,6 +26,7 @@ apex_defaults {
certificate: ":com.android.permission.certificate",
java_libs: [
"framework-permission",
+ "framework-permission-s",
"service-permission",
],
apps: ["PermissionController"],
@@ -41,3 +42,8 @@ android_app_certificate {
name: "com.android.permission.certificate",
certificate: "com.android.permission",
}
+
+filegroup {
+ name: "permission-jarjar-rules",
+ srcs: ["jarjar-rules.txt"],
+}
diff --git a/apex_manifest.json b/apex_manifest.json
index 7960598af..6350d54d6 100644
--- a/apex_manifest.json
+++ b/apex_manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.permission",
- "version": 300000000
+ "version": 309999999
}
diff --git a/framework-s/Android.bp b/framework-s/Android.bp
new file mode 100644
index 000000000..e71cc43f1
--- /dev/null
+++ b/framework-s/Android.bp
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 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.
+
+filegroup {
+ name: "framework-permission-s-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base"],
+}
+
+java_library {
+ name: "framework-permission-s-shared",
+ srcs: [":framework-permission-s-shared-srcs"],
+ libs: [
+ "framework-annotations-lib",
+ "unsupportedappusage",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ min_sdk_version: "30",
+ sdk_version: "module_current",
+}
+
+java_sdk_library {
+ name: "framework-permission-s",
+ defaults: ["framework-module-defaults"],
+ srcs: [
+ ":framework-permission-s-sources",
+ ],
+ libs: [
+ "framework-annotations-lib"
+ ],
+ static_libs: [
+ "framework-permission-s-shared",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ hostdex: true,
+ // Restrict access to implementation library.
+ impl_library_visibility: [
+ "//frameworks/base/apex/permission:__subpackages__",
+ "//packages/modules/Permission:__subpackages__",
+ ],
+ installable: true,
+ jarjar_rules: ":permission-jarjar-rules",
+ min_sdk_version: "30",
+ permitted_packages: [
+ "android.permission",
+ "android.app.role",
+ // For com.android.permission.jarjar.
+ "com.android.permission",
+ ],
+}
diff --git a/framework-s/api/current.txt b/framework-s/api/current.txt
new file mode 100644
index 000000000..4ecc98980
--- /dev/null
+++ b/framework-s/api/current.txt
@@ -0,0 +1,19 @@
+// Signature format: 2.0
+package android.app.role {
+
+ public final class RoleManager {
+ method @NonNull public android.content.Intent createRequestRoleIntent(@NonNull String);
+ method public boolean isRoleAvailable(@NonNull String);
+ method public boolean isRoleHeld(@NonNull String);
+ field public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
+ field public static final String ROLE_BROWSER = "android.app.role.BROWSER";
+ field public static final String ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION";
+ field public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
+ field public static final String ROLE_DIALER = "android.app.role.DIALER";
+ field public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
+ field public static final String ROLE_HOME = "android.app.role.HOME";
+ field public static final String ROLE_SMS = "android.app.role.SMS";
+ }
+
+}
+
diff --git a/framework-s/api/module-lib-current.txt b/framework-s/api/module-lib-current.txt
new file mode 100644
index 000000000..d7c9a2395
--- /dev/null
+++ b/framework-s/api/module-lib-current.txt
@@ -0,0 +1,11 @@
+// Signature format: 2.0
+package android.app.role {
+
+ public final class RoleManager {
+ method @Nullable public String getBrowserRoleHolder(int);
+ method @Nullable public String getSmsRoleHolder(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public boolean setBrowserRoleHolder(@Nullable String, int);
+ }
+
+}
+
diff --git a/framework-s/api/module-lib-removed.txt b/framework-s/api/module-lib-removed.txt
new file mode 100644
index 000000000..d802177e2
--- /dev/null
+++ b/framework-s/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-s/api/removed.txt b/framework-s/api/removed.txt
new file mode 100644
index 000000000..d802177e2
--- /dev/null
+++ b/framework-s/api/removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
new file mode 100644
index 000000000..6778d4826
--- /dev/null
+++ b/framework-s/api/system-current.txt
@@ -0,0 +1,43 @@
+// Signature format: 2.0
+package android.app.role {
+
+ public interface OnRoleHoldersChangedListener {
+ method public void onRoleHoldersChanged(@NonNull String, @NonNull android.os.UserHandle);
+ }
+
+ @Deprecated public abstract class RoleControllerService extends android.app.Service {
+ ctor @Deprecated public RoleControllerService();
+ method @Deprecated @WorkerThread public abstract boolean onAddRoleHolder(@NonNull String, @NonNull String, int);
+ method @Deprecated @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @Deprecated @WorkerThread public abstract boolean onClearRoleHolders(@NonNull String, int);
+ method @Deprecated @WorkerThread public abstract boolean onGrantDefaultRoles();
+ method @Deprecated public abstract boolean onIsApplicationQualifiedForRole(@NonNull String, @NonNull String);
+ method @Deprecated public boolean onIsApplicationVisibleForRole(@NonNull String, @NonNull String);
+ method @Deprecated public abstract boolean onIsRoleVisible(@NonNull String);
+ method @Deprecated @WorkerThread public abstract boolean onRemoveRoleHolder(@NonNull String, @NonNull String, int);
+ field @Deprecated public static final String SERVICE_INTERFACE = "android.app.role.RoleControllerService";
+ }
+
+ public class RoleFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
+ public final class RoleManager {
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void addOnRoleHoldersChangedListenerAsUser(@NonNull java.util.concurrent.Executor, @NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isApplicationVisibleForRole(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
+ method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+ field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
+ }
+
+}
+
diff --git a/framework-s/api/system-removed.txt b/framework-s/api/system-removed.txt
new file mode 100644
index 000000000..d802177e2
--- /dev/null
+++ b/framework-s/api/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl b/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl
new file mode 100644
index 000000000..6cf961fad
--- /dev/null
+++ b/framework-s/java/android/app/role/IOnRoleHoldersChangedListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * @hide
+ */
+oneway interface IOnRoleHoldersChangedListener {
+
+ void onRoleHoldersChanged(String roleName, int userId);
+}
diff --git a/framework-s/java/android/app/role/IRoleController.aidl b/framework-s/java/android/app/role/IRoleController.aidl
new file mode 100644
index 000000000..8a43d7fa9
--- /dev/null
+++ b/framework-s/java/android/app/role/IRoleController.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+oneway interface IRoleController {
+
+ void grantDefaultRoles(in RemoteCallback callback);
+
+ void onAddRoleHolder(in String roleName, in String packageName, int flags,
+ in RemoteCallback callback);
+
+ void onRemoveRoleHolder(in String roleName, in String packageName, int flags,
+ in RemoteCallback callback);
+
+ void onClearRoleHolders(in String roleName, int flags, in RemoteCallback callback);
+
+ void isApplicationQualifiedForRole(in String roleName, in String packageName,
+ in RemoteCallback callback);
+
+ void isApplicationVisibleForRole(in String roleName, in String packageName,
+ in RemoteCallback callback);
+
+ void isRoleVisible(in String roleName, in RemoteCallback callback);
+}
diff --git a/framework-s/java/android/app/role/IRoleManager.aidl b/framework-s/java/android/app/role/IRoleManager.aidl
new file mode 100644
index 000000000..5fc25f042
--- /dev/null
+++ b/framework-s/java/android/app/role/IRoleManager.aidl
@@ -0,0 +1,61 @@
+/*
+ * 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.app.role.IOnRoleHoldersChangedListener;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+interface IRoleManager {
+
+ boolean isRoleAvailable(in String roleName);
+
+ boolean isRoleHeld(in String roleName, in String packageName);
+
+ List<String> getRoleHoldersAsUser(in String roleName, int userId);
+
+ void addRoleHolderAsUser(in String roleName, in String packageName, int flags, int userId,
+ in RemoteCallback callback);
+
+ void removeRoleHolderAsUser(in String roleName, in String packageName, int flags, int userId,
+ in RemoteCallback callback);
+
+ void clearRoleHoldersAsUser(in String roleName, int flags, int userId,
+ in RemoteCallback callback);
+
+ void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId);
+
+ void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener,
+ int userId);
+
+ void setRoleNamesFromController(in List<String> roleNames);
+
+ boolean addRoleHolderFromController(in String roleName, in String packageName);
+
+ boolean removeRoleHolderFromController(in String roleName, in String packageName);
+
+ List<String> getHeldRolesFromController(in String packageName);
+
+ String getBrowserRoleHolder(int userId);
+
+ boolean setBrowserRoleHolder(String packageName, int userId);
+
+ String getSmsRoleHolder(int userId);
+}
diff --git a/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java b/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java
new file mode 100644
index 000000000..5958debc8
--- /dev/null
+++ b/framework-s/java/android/app/role/OnRoleHoldersChangedListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.UserHandle;
+
+/**
+ * Listener for role holder changes.
+ *
+ * @hide
+ */
+@SystemApi
+public interface OnRoleHoldersChangedListener {
+
+ /**
+ * Called when the holders of roles are changed.
+ *
+ * @param roleName the name of the role whose holders are changed
+ * @param user the user for this role holder change
+ */
+ void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user);
+}
diff --git a/framework-s/java/android/app/role/RoleControllerManager.java b/framework-s/java/android/app/role/RoleControllerManager.java
new file mode 100644
index 000000000..93a7ae0c8
--- /dev/null
+++ b/framework-s/java/android/app/role/RoleControllerManager.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2019 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Interface for communicating with the role controller.
+ *
+ * @hide
+ */
+public class RoleControllerManager {
+
+ private static final String LOG_TAG = RoleControllerManager.class.getSimpleName();
+
+ private static final long REQUEST_TIMEOUT_MILLIS = 15 * 1000;
+
+ private static volatile ComponentName sRemoteServiceComponentName;
+
+ private static final Object sRemoteServicesLock = new Object();
+
+ /**
+ * Global remote services (per user) used by all {@link RoleControllerManager managers}.
+ */
+ @GuardedBy("sRemoteServicesLock")
+ private static final SparseArray<ServiceConnector<IRoleController>> sRemoteServices =
+ new SparseArray<>();
+
+ @NonNull
+ private final ServiceConnector<IRoleController> mRemoteService;
+
+ /**
+ * Initialize the remote service component name once so that we can avoid acquiring the
+ * PackageManagerService lock in constructor.
+ *
+ * @see #createWithInitializedRemoteServiceComponentName(Handler, Context)
+ *
+ * @hide
+ */
+ public static void initializeRemoteServiceComponentName(@NonNull Context context) {
+ sRemoteServiceComponentName = getRemoteServiceComponentName(context);
+ }
+
+ /**
+ * Create a {@link RoleControllerManager} instance with the initialized remote service component
+ * name so that we can avoid acquiring the PackageManagerService lock in constructor.
+ *
+ * @see #initializeRemoteServiceComponentName(Context)
+ *
+ * @hide
+ */
+ @NonNull
+ public static RoleControllerManager createWithInitializedRemoteServiceComponentName(
+ @NonNull Handler handler, @NonNull Context context) {
+ return new RoleControllerManager(sRemoteServiceComponentName, handler, context);
+ }
+
+ private RoleControllerManager(@NonNull ComponentName remoteServiceComponentName,
+ @NonNull Handler handler, @NonNull Context context) {
+ synchronized (sRemoteServicesLock) {
+ int userId = context.getUser().getIdentifier();
+ ServiceConnector<IRoleController> remoteService = sRemoteServices.get(userId);
+ if (remoteService == null) {
+ remoteService = new ServiceConnector.Impl<IRoleController>(context,
+ new Intent(RoleControllerService.SERVICE_INTERFACE)
+ .setComponent(remoteServiceComponentName),
+ 0 /* bindingFlags */, userId, IRoleController.Stub::asInterface) {
+
+ @Override
+ protected Handler getJobHandler() {
+ return handler;
+ }
+ };
+ sRemoteServices.put(userId, remoteService);
+ }
+ mRemoteService = remoteService;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public RoleControllerManager(@NonNull Context context) {
+ this(getRemoteServiceComponentName(context), new Handler(Looper.getMainLooper()), context);
+ }
+
+ @NonNull
+ private static ComponentName getRemoteServiceComponentName(@NonNull Context context) {
+ Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE);
+ PackageManager packageManager = context.getPackageManager();
+ intent.setPackage(packageManager.getPermissionControllerPackageName());
+ ServiceInfo serviceInfo = packageManager.resolveService(intent, 0).serviceInfo;
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+
+ /**
+ * @see RoleControllerService#onGrantDefaultRoles()
+ *
+ * @hide
+ */
+ public void grantDefaultRoles(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.grantDefaultRoles(new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "grantDefaultRoles", executor, callback);
+ }
+
+ /**
+ * @see RoleControllerService#onAddRoleHolder(String, String, int)
+ *
+ * @hide
+ */
+ public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.onAddRoleHolder(roleName, packageName, flags,
+ new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "onAddRoleHolder", callback);
+ }
+
+ /**
+ * @see RoleControllerService#onRemoveRoleHolder(String, String, int)
+ *
+ * @hide
+ */
+ public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.onRemoveRoleHolder(roleName, packageName, flags,
+ new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "onRemoveRoleHolder", callback);
+ }
+
+ /**
+ * @see RoleControllerService#onClearRoleHolders(String, int)
+ *
+ * @hide
+ */
+ public void onClearRoleHolders(@NonNull String roleName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.onClearRoleHolders(roleName, flags,
+ new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "onClearRoleHolders", callback);
+ }
+
+ /**
+ * @see RoleControllerService#onIsApplicationVisibleForRole(String, String)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.isApplicationVisibleForRole(roleName, packageName,
+ new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "isApplicationVisibleForRole", executor, callback);
+ }
+
+ /**
+ * @see RoleControllerService#onIsRoleVisible(String)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ public void isRoleVisible(@NonNull String roleName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ AndroidFuture<Bundle> operation = mRemoteService.postAsync(service -> {
+ AndroidFuture<Bundle> future = new AndroidFuture<>();
+ service.isRoleVisible(roleName, new RemoteCallback(future::complete));
+ return future;
+ });
+ propagateCallback(operation, "isRoleVisible", executor, callback);
+ }
+
+ private void propagateCallback(AndroidFuture<Bundle> operation, String opName,
+ @CallbackExecutor @NonNull Executor executor,
+ Consumer<Boolean> destination) {
+ operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ .whenComplete((res, err) -> executor.execute(() -> {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (err != null) {
+ Log.e(LOG_TAG, "Error calling " + opName + "()", err);
+ destination.accept(false);
+ } else {
+ destination.accept(res != null);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }));
+ }
+
+ private void propagateCallback(AndroidFuture<Bundle> operation, String opName,
+ RemoteCallback destination) {
+ operation.orTimeout(REQUEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ .whenComplete((res, err) -> {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (err != null) {
+ Log.e(LOG_TAG, "Error calling " + opName + "()", err);
+ destination.sendResult(null);
+ } else {
+ destination.sendResult(res);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ });
+ }
+}
diff --git a/framework-s/java/android/app/role/RoleControllerService.java b/framework-s/java/android/app/role/RoleControllerService.java
new file mode 100644
index 000000000..cf7872913
--- /dev/null
+++ b/framework-s/java/android/app/role/RoleControllerService.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2019 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Abstract base class for the role controller service.
+ * <p>
+ * Subclass should implement the business logic for role management, including enforcing role
+ * requirements and granting or revoking relevant privileges of roles. This class can only be
+ * implemented by the permission controller app which is registered in {@code PackageManager}.
+ *
+ * @deprecated The role controller service is an internal implementation detail inside role, and it
+ * may be replaced by other mechanisms in the future and no longer be called.
+ *
+ * @hide
+ */
+@Deprecated
+@SystemApi
+public abstract class RoleControllerService extends Service {
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ public static final String SERVICE_INTERFACE = "android.app.role.RoleControllerService";
+
+ private HandlerThread mWorkerThread;
+ private Handler mWorkerHandler;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mWorkerThread = new HandlerThread(RoleControllerService.class.getSimpleName());
+ mWorkerThread.start();
+ mWorkerHandler = new Handler(mWorkerThread.getLooper());
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ mWorkerThread.quitSafely();
+ }
+
+ @Nullable
+ @Override
+ public final IBinder onBind(@Nullable Intent intent) {
+ return new IRoleController.Stub() {
+
+ @Override
+ public void grantDefaultRoles(RemoteCallback callback) {
+ enforceCallerSystemUid("grantDefaultRoles");
+
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mWorkerHandler.post(() -> RoleControllerService.this.grantDefaultRoles(callback));
+ }
+
+ @Override
+ public void onAddRoleHolder(String roleName, String packageName, int flags,
+ RemoteCallback callback) {
+ enforceCallerSystemUid("onAddRoleHolder");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mWorkerHandler.post(() -> RoleControllerService.this.onAddRoleHolder(roleName,
+ packageName, flags, callback));
+ }
+
+ @Override
+ public void onRemoveRoleHolder(String roleName, String packageName, int flags,
+ RemoteCallback callback) {
+ enforceCallerSystemUid("onRemoveRoleHolder");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mWorkerHandler.post(() -> RoleControllerService.this.onRemoveRoleHolder(roleName,
+ packageName, flags, callback));
+ }
+
+ @Override
+ public void onClearRoleHolders(String roleName, int flags, RemoteCallback callback) {
+ enforceCallerSystemUid("onClearRoleHolders");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mWorkerHandler.post(() -> RoleControllerService.this.onClearRoleHolders(roleName,
+ flags, callback));
+ }
+
+ private void enforceCallerSystemUid(@NonNull String methodName) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system process can call " + methodName
+ + "()");
+ }
+ }
+
+ @Override
+ public void isApplicationQualifiedForRole(String roleName, String packageName,
+ RemoteCallback callback) {
+ enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ boolean qualified = onIsApplicationQualifiedForRole(roleName, packageName);
+ callback.sendResult(qualified ? Bundle.EMPTY : null);
+ }
+
+ @Override
+ public void isApplicationVisibleForRole(String roleName, String packageName,
+ RemoteCallback callback) {
+ enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName,
+ "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ boolean visible = onIsApplicationVisibleForRole(roleName, packageName);
+ callback.sendResult(visible ? Bundle.EMPTY : null);
+ }
+
+ @Override
+ public void isRoleVisible(String roleName, RemoteCallback callback) {
+ enforceCallingPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, null);
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ boolean visible = onIsRoleVisible(roleName);
+ callback.sendResult(visible ? Bundle.EMPTY : null);
+ }
+ };
+ }
+
+ private void grantDefaultRoles(@NonNull RemoteCallback callback) {
+ boolean successful = onGrantDefaultRoles();
+ callback.sendResult(successful ? Bundle.EMPTY : null);
+ }
+
+ private void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ boolean successful = onAddRoleHolder(roleName, packageName, flags);
+ callback.sendResult(successful ? Bundle.EMPTY : null);
+ }
+
+ private void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ boolean successful = onRemoveRoleHolder(roleName, packageName, flags);
+ callback.sendResult(successful ? Bundle.EMPTY : null);
+ }
+
+ private void onClearRoleHolders(@NonNull String roleName,
+ @RoleManager.ManageHoldersFlags int flags, @NonNull RemoteCallback callback) {
+ boolean successful = onClearRoleHolders(roleName, flags);
+ callback.sendResult(successful ? Bundle.EMPTY : null);
+ }
+
+ /**
+ * Called by system to grant default permissions and roles.
+ * <p>
+ * This is typically when creating a new user or upgrading either system or
+ * permission controller package
+ *
+ * @return whether this call was successful
+ */
+ @WorkerThread
+ public abstract boolean onGrantDefaultRoles();
+
+ /**
+ * Add a specific application to the holders of a role. If the role is exclusive, the previous
+ * holder will be replaced.
+ * <p>
+ * Implementation should enforce the role requirements and grant or revoke the relevant
+ * privileges of roles.
+ *
+ * @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 flags optional behavior flags
+ *
+ * @return whether this call was successful
+ *
+ * @see RoleManager#addRoleHolderAsUser(String, String, int, UserHandle, Executor,
+ * RemoteCallback)
+ */
+ @WorkerThread
+ public abstract boolean onAddRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags);
+
+ /**
+ * Remove a specific application from the holders of a role.
+ *
+ * @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 flags optional behavior flags
+ *
+ * @return whether this call was successful
+ *
+ * @see RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, Executor,
+ * RemoteCallback)
+ */
+ @WorkerThread
+ public abstract boolean onRemoveRoleHolder(@NonNull String roleName,
+ @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags);
+
+ /**
+ * Remove all holders of a role.
+ *
+ * @param roleName the name of the role to remove role holders for
+ * @param flags optional behavior flags
+ *
+ * @return whether this call was successful
+ *
+ * @see RoleManager#clearRoleHoldersAsUser(String, int, UserHandle, Executor, RemoteCallback)
+ */
+ @WorkerThread
+ public abstract boolean onClearRoleHolders(@NonNull String roleName,
+ @RoleManager.ManageHoldersFlags int flags);
+
+ /**
+ * Check whether an application is qualified for a role.
+ *
+ * @param roleName name of the role to check for
+ * @param packageName package name of the application to check for
+ *
+ * @return whether the application is qualified for the role
+ *
+ * @deprecated Implement {@link #onIsApplicationVisibleForRole(String, String)} instead.
+ */
+ @Deprecated
+ public abstract boolean onIsApplicationQualifiedForRole(@NonNull String roleName,
+ @NonNull String packageName);
+
+ /**
+ * Check whether an application is visible for a role.
+ *
+ * While an application can be qualified for a role, it can still stay hidden from user (thus
+ * not visible). If an application is visible for a role, we may show things related to the role
+ * for it, e.g. showing an entry pointing to the role settings in its application info page.
+ *
+ * @param roleName name of the role to check for
+ * @param packageName package name of the application to check for
+ *
+ * @return whether the application is visible for the role
+ */
+ public boolean onIsApplicationVisibleForRole(@NonNull String roleName,
+ @NonNull String packageName) {
+ return onIsApplicationQualifiedForRole(roleName, packageName);
+ }
+
+ /**
+ * Check whether a role should be visible to user.
+ *
+ * @param roleName name of the role to check for
+ *
+ * @return whether the role should be visible to user
+ */
+ public abstract boolean onIsRoleVisible(@NonNull String roleName);
+}
diff --git a/framework-s/java/android/app/role/RoleFrameworkInitializer.java b/framework-s/java/android/app/role/RoleFrameworkInitializer.java
new file mode 100644
index 000000000..7a97770ec
--- /dev/null
+++ b/framework-s/java/android/app/role/RoleFrameworkInitializer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class holding initialization code for role in the permission module.
+ *
+ * @hide
+ */
+@SystemApi
+public class RoleFrameworkInitializer {
+ private RoleFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers
+ * {@link RoleManager} to {@link Context}, so that {@link Context#getSystemService} can return
+ * it.
+ *
+ * <p>If this is called from other places, it throws a {@link IllegalStateException).
+ */
+ public static void registerServiceWrappers() {
+ SystemServiceRegistry.registerContextAwareService(Context.ROLE_SERVICE, RoleManager.class,
+ (context, serviceBinder) -> new RoleManager(context,
+ IRoleManager.Stub.asInterface(serviceBinder)));
+ }
+}
diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
new file mode 100644
index 000000000..ceccc4cfc
--- /dev/null
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -0,0 +1,773 @@
+/*
+ * 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.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * 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 <a href="{@docRoot}reference/androidx/core/role/package-summary.html">AndroidX Role
+ * library</a>.
+ * <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 android.app.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 assistant app role.
+ *
+ * @see android.service.voice.VoiceInteractionService
+ */
+ public static final String ROLE_ASSISTANT = "android.app.role.ASSISTANT";
+
+ /**
+ * The name of the browser role.
+ *
+ * @see Intent#CATEGORY_APP_BROWSER
+ */
+ public static final String ROLE_BROWSER = "android.app.role.BROWSER";
+
+ /**
+ * The name of the dialer role.
+ *
+ * @see Intent#ACTION_DIAL
+ * @see android.telecom.InCallService
+ */
+ public static final String ROLE_DIALER = "android.app.role.DIALER";
+
+ /**
+ * The name of the SMS role.
+ *
+ * @see Intent#CATEGORY_APP_MESSAGING
+ */
+ public static final String ROLE_SMS = "android.app.role.SMS";
+
+ /**
+ * The name of the emergency role
+ */
+ public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
+
+ /**
+ * The name of the home role.
+ *
+ * @see Intent#CATEGORY_HOME
+ */
+ public static final String ROLE_HOME = "android.app.role.HOME";
+
+ /**
+ * The name of the call redirection role.
+ * <p>
+ * A call redirection app provides a means to re-write the phone number for an outgoing call to
+ * place the call through a call redirection service.
+ *
+ * @see android.telecom.CallRedirectionService
+ */
+ public static final String ROLE_CALL_REDIRECTION = "android.app.role.CALL_REDIRECTION";
+
+ /**
+ * The name of the call screening and caller id role.
+ *
+ * @see android.telecom.CallScreeningService
+ */
+ public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
+
+ /**
+ * @hide
+ */
+ @IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP })
+ public @interface ManageHoldersFlags {}
+
+ /**
+ * Flag parameter for {@link #addRoleHolderAsUser}, {@link #removeRoleHolderAsUser} and
+ * {@link #clearRoleHoldersAsUser} to indicate that apps should not be killed when changing
+ * their role holder status.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1;
+
+ /**
+ * 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 permission required to manage records of role holders in {@link RoleManager} directly.
+ *
+ * @hide
+ */
+ public static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
+ "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
+
+ @NonNull
+ private final Context mContext;
+
+ @NonNull
+ private final IRoleManager mService;
+
+ @GuardedBy("mListenersLock")
+ @NonNull
+ private final SparseArray<ArrayMap<OnRoleHoldersChangedListener,
+ OnRoleHoldersChangedListenerDelegate>> mListeners = new SparseArray<>();
+ @NonNull
+ private final Object mListenersLock = new Object();
+
+ @GuardedBy("mRoleControllerManagerLock")
+ @Nullable
+ private RoleControllerManager mRoleControllerManager;
+ private final Object mRoleControllerManagerLock = new Object();
+
+ /**
+ * Create a new instance of this class.
+ *
+ * @param context the {@link Context}
+ * @param service the {@link IRoleManager} service
+ *
+ * @hide
+ */
+ public RoleManager(@NonNull Context context, @NonNull IRoleManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns an {@code Intent} suitable for passing to
+ * {@link android.app.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 android.app.Activity#RESULT_OK}, otherwise it will be
+ * {@link android.app.Activity#RESULT_CANCELED}.
+ *
+ * @param roleName the name of requested role
+ *
+ * @return the {@code Intent} to prompt user to grant the role
+ */
+ @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(Intent.EXTRA_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
+ */
+ 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
+ */
+ 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}.
+ *
+ * @param roleName the name of the role to get the role holder for
+ *
+ * @return a list of package names of the role holders, or an empty list if none.
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ *
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public List<String> getRoleHolders(@NonNull String roleName) {
+ return getRoleHoldersAsUser(roleName, Process.myUserHandle());
+ }
+
+ /**
+ * 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 a list of package names of the role holders, or an empty list if none.
+ *
+ * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
+ *
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public List<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ try {
+ return mService.getRoleHoldersAsUser(roleName, user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * 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 flags optional behavior flags
+ * @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
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @ManageHoldersFlags int flags, @NonNull UserHandle user,
+ @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ try {
+ mService.addRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
+ createRemoteCallback(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 flags optional behavior flags
+ * @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
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ * @see #clearRoleHoldersAsUser(String, int, UserHandle, Executor, Consumer)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @ManageHoldersFlags int flags, @NonNull UserHandle user,
+ @CallbackExecutor @NonNull Executor executor, @NonNull Consumer<Boolean> callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ try {
+ mService.removeRoleHolderAsUser(roleName, packageName, flags, user.getIdentifier(),
+ createRemoteCallback(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 flags optional behavior flags
+ * @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
+ *
+ * @see #getRoleHoldersAsUser(String, UserHandle)
+ * @see #addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ * @see #removeRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void clearRoleHoldersAsUser(@NonNull String roleName, @ManageHoldersFlags int flags,
+ @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ try {
+ mService.clearRoleHoldersAsUser(roleName, flags, user.getIdentifier(),
+ createRemoteCallback(executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @NonNull
+ private static RemoteCallback createRemoteCallback(@NonNull Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ return new RemoteCallback(result -> executor.execute(() -> {
+ boolean successful = result != null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ callback.accept(successful);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }));
+ }
+
+ /**
+ * Add a listener to observe role holder changes
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param executor the {@code Executor} to call the listener on.
+ * @param listener the listener to be added
+ * @param user the user to add the listener for
+ *
+ * @see #removeOnRoleHoldersChangedListenerAsUser(OnRoleHoldersChangedListener, UserHandle)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
+ @SystemApi
+ public void addOnRoleHoldersChangedListenerAsUser(@CallbackExecutor @NonNull Executor executor,
+ @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
+ Objects.requireNonNull(executor, "executor cannot be null");
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ int userId = user.getIdentifier();
+ synchronized (mListenersLock) {
+ ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
+ mListeners.get(userId);
+ if (listeners == null) {
+ listeners = new ArrayMap<>();
+ mListeners.put(userId, listeners);
+ } else {
+ if (listeners.containsKey(listener)) {
+ return;
+ }
+ }
+ OnRoleHoldersChangedListenerDelegate listenerDelegate =
+ new OnRoleHoldersChangedListenerDelegate(executor, listener);
+ try {
+ mService.addOnRoleHoldersChangedListenerAsUser(listenerDelegate, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ listeners.put(listener, listenerDelegate);
+ }
+ }
+
+ /**
+ * Remove a listener observing role holder changes
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.OBSERVE_ROLE_HOLDERS} and if the user id is not the current user
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+ *
+ * @param listener the listener to be removed
+ * @param user the user to remove the listener for
+ *
+ * @see #addOnRoleHoldersChangedListenerAsUser(Executor, OnRoleHoldersChangedListener,
+ * UserHandle)
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS)
+ @SystemApi
+ public void removeOnRoleHoldersChangedListenerAsUser(
+ @NonNull OnRoleHoldersChangedListener listener, @NonNull UserHandle user) {
+ Objects.requireNonNull(listener, "listener cannot be null");
+ Objects.requireNonNull(user, "user cannot be null");
+ int userId = user.getIdentifier();
+ synchronized (mListenersLock) {
+ ArrayMap<OnRoleHoldersChangedListener, OnRoleHoldersChangedListenerDelegate> listeners =
+ mListeners.get(userId);
+ if (listeners == null) {
+ return;
+ }
+ OnRoleHoldersChangedListenerDelegate listenerDelegate = listeners.get(listener);
+ if (listenerDelegate == null) {
+ return;
+ }
+ try {
+ mService.removeOnRoleHoldersChangedListenerAsUser(listenerDelegate,
+ user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ listeners.remove(listener);
+ if (listeners.isEmpty()) {
+ mListeners.remove(userId);
+ }
+ }
+ }
+
+ /**
+ * Set the names of all the available roles. Should only be called from
+ * {@link android.app.role.RoleControllerService}.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
+ *
+ * @param roleNames the names of all the available roles
+ *
+ * @deprecated This is only usable by the role controller service, which is an internal
+ * implementation detail inside role.
+ *
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+ @SystemApi
+ public void setRoleNamesFromController(@NonNull List<String> roleNames) {
+ Objects.requireNonNull(roleNames, "roleNames cannot be null");
+ try {
+ mService.setRoleNamesFromController(roleNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Add a specific application to the holders of a role, only modifying records inside
+ * {@link RoleManager}. Should only be called from
+ * {@link android.app.role.RoleControllerService}.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
+ *
+ * @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
+ *
+ * @return whether the operation was successful, and will also be {@code true} if a matching
+ * role holder is already found.
+ *
+ * @see #getRoleHolders(String)
+ * @see #removeRoleHolderFromController(String, String)
+ *
+ * @deprecated This is only usable by the role controller service, which is an internal
+ * implementation detail inside role.
+ *
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+ @SystemApi
+ public boolean addRoleHolderFromController(@NonNull String roleName,
+ @NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ try {
+ return mService.addRoleHolderFromController(roleName, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove a specific application from the holders of a role, only modifying records inside
+ * {@link RoleManager}. Should only be called from
+ * {@link android.app.role.RoleControllerService}.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@link #PERMISSION_MANAGE_ROLES_FROM_CONTROLLER}.
+ *
+ * @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
+ *
+ * @return whether the operation was successful, and will also be {@code true} if no matching
+ * role holder was found to remove.
+ *
+ * @see #getRoleHolders(String)
+ * @see #addRoleHolderFromController(String, String)
+ *
+ * @deprecated This is only usable by the role controller service, which is an internal
+ * implementation detail inside role.
+ *
+ * @hide
+ */
+ @Deprecated
+ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+ @SystemApi
+ public boolean removeRoleHolderFromController(@NonNull String roleName,
+ @NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ try {
+ return mService.removeRoleHolderFromController(roleName, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the list of all roles that the given package is currently holding
+ *
+ * @param packageName the package name
+ * @return the list of role names
+ *
+ * @deprecated This is only usable by the role controller service, which is an internal
+ * implementation detail inside role.
+ *
+ * @hide
+ */
+ @Deprecated
+ @NonNull
+ @RequiresPermission(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)
+ @SystemApi
+ public List<String> getHeldRolesFromController(@NonNull String packageName) {
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ try {
+ return mService.getHeldRolesFromController(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get the role holder of {@link #ROLE_BROWSER} without requiring
+ * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as in
+ * {@link android.content.pm.PackageManager#getDefaultBrowserPackageNameAsUser(int)}
+ *
+ * @param userId the user ID
+ * @return the package name of the default browser, or {@code null} if none
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public String getBrowserRoleHolder(@UserIdInt int userId) {
+ try {
+ return mService.getBrowserRoleHolder(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the role holder of {@link #ROLE_BROWSER} requiring
+ * {@link Manifest.permission.SET_PREFERRED_APPLICATIONS} instead of
+ * {@link Manifest.permission#MANAGE_ROLE_HOLDERS}, as in
+ * {@link android.content.pm.PackageManager#setDefaultBrowserPackageNameAsUser(String, int)}
+ *
+ * @param packageName the package name of the default browser, or {@code null} if none
+ * @param userId the user ID
+ * @return whether the default browser was set successfully
+ *
+ * @hide
+ */
+ @Nullable
+ @RequiresPermission(Manifest.permission.SET_PREFERRED_APPLICATIONS)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public boolean setBrowserRoleHolder(@Nullable String packageName, @UserIdInt int userId) {
+ try {
+ return mService.setBrowserRoleHolder(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Allows getting the role holder for {@link #ROLE_SMS} without requiring
+ * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as in
+ * {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)}.
+ *
+ * @param userId the user ID to get the default SMS package for
+ * @return the package name of the default SMS app, or {@code null} if none
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public String getSmsRoleHolder(@UserIdInt int userId) {
+ try {
+ return mService.getSmsRoleHolder(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Check whether a role should be visible to user.
+ *
+ * @param roleName name of the role to check for
+ * @param executor the executor to execute callback on
+ * @param callback the callback to receive whether the role should be visible to user
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void isRoleVisible(@NonNull String roleName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ getRoleControllerManager().isRoleVisible(roleName, executor, callback);
+ }
+
+ /**
+ * Check whether an application is visible for a role.
+ *
+ * While an application can be qualified for a role, it can still stay hidden from user (thus
+ * not visible). If an application is visible for a role, we may show things related to the role
+ * for it, e.g. showing an entry pointing to the role settings in its application info page.
+ *
+ * @param roleName the name of the role to check for
+ * @param packageName the package name of the application to check for
+ * @param executor the executor to execute callback on
+ * @param callback the callback to receive whether the application is visible for the role
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @SystemApi
+ public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ getRoleControllerManager().isApplicationVisibleForRole(roleName, packageName, executor,
+ callback);
+ }
+
+ @NonNull
+ private RoleControllerManager getRoleControllerManager() {
+ synchronized (mRoleControllerManagerLock) {
+ if (mRoleControllerManager == null) {
+ mRoleControllerManager = new RoleControllerManager(mContext);
+ }
+ return mRoleControllerManager;
+ }
+ }
+
+ private static class OnRoleHoldersChangedListenerDelegate
+ extends IOnRoleHoldersChangedListener.Stub {
+
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final OnRoleHoldersChangedListener mListener;
+
+ OnRoleHoldersChangedListenerDelegate(@NonNull Executor executor,
+ @NonNull OnRoleHoldersChangedListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() ->
+ mListener.onRoleHoldersChanged(roleName, UserHandle.of(userId)));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+}
diff --git a/framework-s/java/android/app/role/TEST_MAPPING b/framework-s/java/android/app/role/TEST_MAPPING
new file mode 100644
index 000000000..f8f140dd7
--- /dev/null
+++ b/framework-s/java/android/app/role/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsRoleTestCases",
+ "options": [
+ {
+ "include-filter": "android.app.role.cts.RoleManagerTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 1a2d5d541..52a61674a 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -19,6 +19,7 @@ filegroup {
"java/**/*.aidl",
],
path: "java",
+ visibility: ["//frameworks/base"],
}
java_sdk_library {
@@ -39,6 +40,7 @@ java_sdk_library {
"com.android.permission",
"test_com.android.permission",
],
+ min_sdk_version: "30",
permitted_packages: [
"android.permission",
"android.app.role",
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
new file mode 100644
index 000000000..4729ed13d
--- /dev/null
+++ b/jarjar-rules.txt
@@ -0,0 +1,5 @@
+rule android.os.HandlerExecutor com.android.permission.jarjar.@0
+rule android.util.IndentingPrintWriter com.android.permission.jarjar.@0
+rule com.android.internal.** com.android.permission.jarjar.@0
+rule com.android.modules.** com.android.permission.jarjar.@0
+rule com.android.role.*Proto com.android.permission.jarjar.@0
diff --git a/service/Android.bp b/service/Android.bp
index cc6f2019a..d0fc5b9d7 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -18,6 +18,57 @@ filegroup {
"java/**/*.java",
],
path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+filegroup {
+ name: "service-permission-protos",
+ srcs: [
+ "proto/**/*.proto",
+ ],
+ visibility: ["//frameworks/base"],
+}
+
+gensrcs {
+ name: "service-permission-javastream-protos",
+ depfile: true,
+
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
+ ],
+
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ " --plugin=$(location protoc-gen-javastream) " +
+ " --dependency_out=$(depfile) " +
+ " --javastream_out=$(genDir)/$(in) " +
+ " -Iexternal/protobuf/src " +
+ " -I . " +
+ " $(in) " +
+ "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+ srcs: [
+ ":service-permission-protos",
+ ],
+ output_extension: "srcjar",
+}
+
+java_library {
+ name: "service-permission-shared",
+ srcs: [":service-permission-shared-srcs"],
+ libs: [
+ "framework-annotations-lib",
+ "framework-permission-s-shared",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ min_sdk_version: "30",
+ sdk_version: "system_server_current",
}
java_sdk_library {
@@ -31,10 +82,20 @@ java_sdk_library {
],
srcs: [
":service-permission-sources",
+ ":service-permission-javastream-protos",
],
libs: [
"framework-permission",
+ "framework-permission-s.impl",
+ "framework-permission-s-shared",
+ ],
+ static_libs: [
+ "modules-utils-os",
+ "service-permission-shared",
],
+ jarjar_rules: ":permission-jarjar-rules",
+ min_sdk_version: "30",
+ sdk_version: "system_server_current",
apex_available: [
"com.android.permission",
"test_com.android.permission",
diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt
index c76cc3275..b1869c2c7 100644
--- a/service/api/system-server-current.txt
+++ b/service/api/system-server-current.txt
@@ -26,6 +26,14 @@ package com.android.permission.persistence {
}
+package com.android.role {
+
+ public interface RoleManagerLocal {
+ method @NonNull public java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getRolesAndHolders(int);
+ }
+
+}
+
package com.android.role.persistence {
public interface RolesPersistence {
diff --git a/service/java/com/android/permission/compat/UserHandleCompat.java b/service/java/com/android/permission/compat/UserHandleCompat.java
new file mode 100644
index 000000000..7c711d301
--- /dev/null
+++ b/service/java/com/android/permission/compat/UserHandleCompat.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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 com.android.permission.compat;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+
+/**
+ * Helper for accessing features in {@link UserHandle}.
+ */
+public final class UserHandleCompat {
+ /**
+ * A user ID to indicate all users on the device.
+ */
+ public static final int USER_ALL = UserHandle.ALL.getIdentifier();
+
+ /**
+ * A user ID to indicate the "system" user of the device.
+ */
+ public static final int USER_SYSTEM = UserHandle.SYSTEM.getIdentifier();
+
+ private UserHandleCompat() {}
+
+ /**
+ * Get the user ID of a given UID.
+ *
+ * @param uid the UID
+ * @return the user ID
+ */
+ @UserIdInt
+ public static int getUserId(int uid) {
+ return UserHandle.getUserHandleForUid(uid).getIdentifier();
+ }
+}
diff --git a/service/java/com/android/permission/compat/package-info.java b/service/java/com/android/permission/compat/package-info.java
new file mode 100644
index 000000000..c89cc8eab
--- /dev/null
+++ b/service/java/com/android/permission/compat/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.permission.compat;
diff --git a/service/java/com/android/permission/util/ArrayUtils.java b/service/java/com/android/permission/util/ArrayUtils.java
new file mode 100644
index 000000000..5d5cd7820
--- /dev/null
+++ b/service/java/com/android/permission/util/ArrayUtils.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 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 com.android.permission.util;
+
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Array utilities.
+ */
+public final class ArrayUtils {
+ private ArrayUtils() {}
+
+ /**
+ * @see java.util.List#contains(Object)
+ */
+ public static <T> boolean contains(@Nullable T[] array, T value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Get the first element of an array, or {@code null} if none.
+ *
+ * @param array the array
+ * @param <T> the type of the elements of the array
+ * @return first element of an array, or {@code null} if none
+ */
+ public static <T> T firstOrNull(@Nullable T[] array) {
+ return !isEmpty(array) ? array[0] : null;
+ }
+
+ /**
+ * @see java.util.List#indexOf(Object)
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) {
+ return -1;
+ }
+ final int length = array.length;
+ for (int i = 0; i < length; i++) {
+ final T element = array[i];
+ if (Objects.equals(element, value)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @see java.util.List#isEmpty()
+ */
+ public static <T> boolean isEmpty(@Nullable T[] array) {
+ return array == null || array.length == 0;
+ }
+}
diff --git a/service/java/com/android/permission/util/BackgroundThread.java b/service/java/com/android/permission/util/BackgroundThread.java
new file mode 100644
index 000000000..7308eec98
--- /dev/null
+++ b/service/java/com/android/permission/util/BackgroundThread.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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 com.android.permission.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton background thread.
+ */
+public class BackgroundThread extends HandlerThread {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static BackgroundThread sInstance;
+ @GuardedBy("sLock")
+ private static Handler sHandler;
+ @GuardedBy("sLock")
+ private static Executor sExecutor;
+
+ private BackgroundThread() {
+ super(BackgroundThread.class.getName());
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureInstanceLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /**
+ * Get the singleton instance of thi class.
+ *
+ * @return the singleton instance of thi class
+ */
+ @NonNull
+ public static BackgroundThread get() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get the {@link Handler} for this thread.
+ *
+ * @return the {@link Handler} for this thread.
+ */
+ @NonNull
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sHandler;
+ }
+ }
+
+ /**
+ * Get the {@link Executor} for this thread.
+ *
+ * @return the {@link Executor} for this thread.
+ */
+ @NonNull
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sExecutor;
+ }
+ }
+}
diff --git a/service/java/com/android/permission/util/CollectionUtils.java b/service/java/com/android/permission/util/CollectionUtils.java
new file mode 100644
index 000000000..ea4952404
--- /dev/null
+++ b/service/java/com/android/permission/util/CollectionUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 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 com.android.permission.util;
+
+import android.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * {@link Collection} utilities.
+ */
+public class CollectionUtils {
+ private CollectionUtils() {}
+
+ /**
+ * Get the first element of a {@link List}, or {@code null} if none.
+ *
+ * @param list the {@link List}, or {@code null}
+ * @param <E> the element type of the {@link List}
+ * @return the first element of the {@link List}, or {@code 0} if none
+ */
+ @Nullable
+ public static <E> E firstOrNull(@Nullable List<E> list) {
+ return !isEmpty(list) ? list.get(0) : null;
+ }
+
+ /**
+ * Check whether a {@link Collection} is empty or {@code null}.
+ *
+ * @param collection the {@link Collection}, or {@code null}
+ * @return whether the {@link Collection} is empty or {@code null}
+ */
+ public static boolean isEmpty(@Nullable Collection<?> collection) {
+ return collection == null || collection.isEmpty();
+ }
+
+ /**
+ * Get the size of a {@link Collection}, or {@code 0} if {@code null}.
+ *
+ * @param collection the {@link Collection}, or {@code null}
+ * @return the size of the {@link Collection}, or {@code 0} if {@code null}
+ */
+ public static int size(@Nullable Collection<?> collection) {
+ return collection != null ? collection.size() : 0;
+ }
+
+ /**
+ * Get the size of a {@link Map}, or {@code 0} if {@code null}.
+ *
+ * @param collection the {@link Map}, or {@code null}
+ * @return the size of the {@link Map}, or {@code 0} if {@code null}
+ */
+ public static int size(@Nullable Map<?, ?> collection) {
+ return collection != null ? collection.size() : 0;
+ }
+}
diff --git a/service/java/com/android/permission/util/ForegroundThread.java b/service/java/com/android/permission/util/ForegroundThread.java
new file mode 100644
index 000000000..cd6f60570
--- /dev/null
+++ b/service/java/com/android/permission/util/ForegroundThread.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 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 com.android.permission.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton foreground thread.
+ */
+public class ForegroundThread extends HandlerThread {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static ForegroundThread sInstance;
+ @GuardedBy("sLock")
+ private static Handler sHandler;
+ @GuardedBy("sLock")
+ private static Executor sExecutor;
+
+ private ForegroundThread() {
+ super(ForegroundThread.class.getName());
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureInstanceLocked() {
+ if (sInstance == null) {
+ sInstance = new ForegroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /**
+ * Get the singleton instance of thi class.
+ *
+ * @return the singleton instance of thi class
+ */
+ @NonNull
+ public static ForegroundThread get() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get the {@link Handler} for this thread.
+ *
+ * @return the {@link Handler} for this thread.
+ */
+ @NonNull
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sHandler;
+ }
+ }
+
+ /**
+ * Get the {@link Executor} for this thread.
+ *
+ * @return the {@link Executor} for this thread.
+ */
+ @NonNull
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureInstanceLocked();
+ return sExecutor;
+ }
+ }
+}
diff --git a/service/java/com/android/permission/util/ThrottledRunnable.java b/service/java/com/android/permission/util/ThrottledRunnable.java
new file mode 100644
index 000000000..ba1c3939f
--- /dev/null
+++ b/service/java/com/android/permission/util/ThrottledRunnable.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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 com.android.permission.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A throttled runnable that can wrap around a runnable and throttle calls to its run().
+ *
+ * The throttling logic makes sure that the original runnable will be called only after the
+ * specified interval passes since the last actual call. The first call in a while (after the
+ * specified interval passes since the last actual call) will always result in the original runnable
+ * being called immediately, and then subsequent calls will start to be throttled. It is guaranteed
+ * that any call to this throttled runnable will always result in the original runnable being called
+ * afterwards, within the specified interval.
+ */
+public class ThrottledRunnable implements Runnable {
+
+ @NonNull
+ private final Handler mHandler;
+ private final long mIntervalMillis;
+ @NonNull
+ private final Runnable mRunnable;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private long mScheduledUptimeMillis;
+
+ public ThrottledRunnable(@NonNull Handler handler, long intervalMillis,
+ @NonNull Runnable runnable) {
+ mHandler = handler;
+ mIntervalMillis = intervalMillis;
+ mRunnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mHandler.hasCallbacks(mRunnable)) {
+ // We have a scheduled runnable.
+ return;
+ }
+ long currentUptimeMillis = SystemClock.uptimeMillis();
+ if (mScheduledUptimeMillis == 0
+ || currentUptimeMillis > mScheduledUptimeMillis + mIntervalMillis) {
+ // First time in a while, schedule immediately.
+ mScheduledUptimeMillis = currentUptimeMillis;
+ } else {
+ // We were scheduled not long ago, so schedule with delay for throttling.
+ mScheduledUptimeMillis = mScheduledUptimeMillis + mIntervalMillis;
+ }
+ mHandler.postAtTime(mRunnable, mScheduledUptimeMillis);
+ }
+ }
+}
diff --git a/service/java/com/android/permission/util/package-info.java b/service/java/com/android/permission/util/package-info.java
new file mode 100644
index 000000000..18fada534
--- /dev/null
+++ b/service/java/com/android/permission/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.permission.util;
diff --git a/service/java/com/android/role/RoleManagerLocal.java b/service/java/com/android/role/RoleManagerLocal.java
new file mode 100644
index 000000000..e243e2e0d
--- /dev/null
+++ b/service/java/com/android/role/RoleManagerLocal.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 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 com.android.role;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Internal calls into {@link RoleService}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface RoleManagerLocal {
+ /**
+ * Get all roles and their holders.
+ *
+ * @param userId The user to query to roles for
+ *
+ * @return The roles and their holders
+ */
+ @NonNull
+ Map<String, Set<String>> getRolesAndHolders(@UserIdInt int userId);
+}
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
new file mode 100644
index 000000000..3cbd9a8d4
--- /dev/null
+++ b/service/java/com/android/role/RoleService.java
@@ -0,0 +1,734 @@
+/*
+ * 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 com.android.role;
+
+import android.Manifest;
+import android.annotation.AnyThread;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.app.AppOpsManager;
+import android.app.role.IOnRoleHoldersChangedListener;
+import android.app.role.IRoleManager;
+import android.app.role.RoleControllerManager;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.permission.compat.UserHandleCompat;
+import com.android.permission.util.ArrayUtils;
+import com.android.permission.util.CollectionUtils;
+import com.android.permission.util.ForegroundThread;
+import com.android.permission.util.ThrottledRunnable;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.SystemService;
+import com.android.server.role.RoleServicePlatformHelper;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Service for role management.
+ *
+ * @see RoleManager
+ */
+public class RoleService extends SystemService implements RoleUserState.Callback {
+ private static final String LOG_TAG = RoleService.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final long GRANT_DEFAULT_ROLES_INTERVAL_MILLIS = 1000;
+
+ @NonNull
+ private final AppOpsManager mAppOpsManager;
+ @NonNull
+ private final UserManager mUserManager;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @NonNull
+ private final RoleServicePlatformHelper mPlatformHelper;
+
+ /**
+ * Maps user id to its state.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<RoleUserState> mUserStates = new SparseArray<>();
+
+ /**
+ * Maps user id to its controller.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<RoleControllerManager> mControllers = new SparseArray<>();
+
+ /**
+ * Maps user id to its list of listeners.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<RemoteCallbackList<IOnRoleHoldersChangedListener>> mListeners =
+ new SparseArray<>();
+
+ @NonNull
+ private final Handler mListenerHandler = ForegroundThread.getHandler();
+
+ /**
+ * Maps user id to its throttled runnable for granting default roles.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<ThrottledRunnable> mGrantDefaultRolesThrottledRunnables =
+ new SparseArray<>();
+
+ public RoleService(@NonNull Context context) {
+ super(context);
+
+ mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class);
+
+ RoleControllerManager.initializeRemoteServiceComponentName(context);
+
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mUserManager = context.getSystemService(UserManager.class);
+
+ LocalManagerRegistry.addManager(RoleManagerLocal.class, new Local());
+
+ registerUserRemovedReceiver();
+ }
+
+ private void registerUserRemovedReceiver() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ getContext().registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ if (TextUtils.equals(intent.getAction(), Intent.ACTION_USER_REMOVED)) {
+ int userId = intent.<UserHandle>getParcelableExtra(Intent.EXTRA_USER)
+ .getIdentifier();
+ onRemoveUser(userId);
+ }
+ }
+ }, intentFilter, null, null);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.ROLE_SERVICE, new Stub());
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ getContext().registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int userId = UserHandleCompat.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Packages changed - re-running initial grants for user "
+ + userId);
+ }
+ if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
+ && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // Package is being upgraded - we're about to get ACTION_PACKAGE_ADDED
+ return;
+ }
+ maybeGrantDefaultRolesAsync(userId);
+ }
+ }, intentFilter, null, null);
+ }
+
+ @Override
+ public void onUserStarting(@NonNull TargetUser user) {
+ maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier());
+ }
+
+ @MainThread
+ private void maybeGrantDefaultRolesSync(@UserIdInt int userId) {
+ AndroidFuture<Void> future = maybeGrantDefaultRolesInternal(userId);
+ try {
+ future.get(30, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Failed to grant default roles for user " + userId, e);
+ }
+ }
+
+ private void maybeGrantDefaultRolesAsync(@UserIdInt int userId) {
+ ThrottledRunnable runnable;
+ synchronized (mLock) {
+ runnable = mGrantDefaultRolesThrottledRunnables.get(userId);
+ if (runnable == null) {
+ runnable = new ThrottledRunnable(ForegroundThread.getHandler(),
+ GRANT_DEFAULT_ROLES_INTERVAL_MILLIS,
+ () -> maybeGrantDefaultRolesInternal(userId));
+ mGrantDefaultRolesThrottledRunnables.put(userId, runnable);
+ }
+ }
+ runnable.run();
+ }
+
+ @AnyThread
+ @NonNull
+ private AndroidFuture<Void> maybeGrantDefaultRolesInternal(@UserIdInt int userId) {
+ RoleUserState userState = getOrCreateUserState(userId);
+ String oldPackagesHash = userState.getPackagesHash();
+ String newPackagesHash = mPlatformHelper.computePackageStateHash(userId);
+ if (Objects.equals(oldPackagesHash, newPackagesHash)) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Already granted default roles for packages hash "
+ + newPackagesHash);
+ }
+ return AndroidFuture.completedFuture(null);
+ }
+
+ // Some package state has changed, so grant default roles again.
+ Log.i(LOG_TAG, "Granting default roles...");
+ AndroidFuture<Void> future = new AndroidFuture<>();
+ getOrCreateController(userId).grantDefaultRoles(ForegroundThread.getExecutor(),
+ successful -> {
+ if (successful) {
+ userState.setPackagesHash(newPackagesHash);
+ future.complete(null);
+ } else {
+ future.completeExceptionally(new RuntimeException());
+ }
+ });
+ return future;
+ }
+
+ @NonNull
+ private RoleUserState getOrCreateUserState(@UserIdInt int userId) {
+ synchronized (mLock) {
+ RoleUserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new RoleUserState(userId, mPlatformHelper, this);
+ mUserStates.put(userId, userState);
+ }
+ return userState;
+ }
+ }
+
+ @NonNull
+ private RoleControllerManager getOrCreateController(@UserIdInt int userId) {
+ synchronized (mLock) {
+ RoleControllerManager controller = mControllers.get(userId);
+ if (controller == null) {
+ Context systemContext = getContext();
+ Context context;
+ try {
+ context = systemContext.createPackageContextAsUser(
+ systemContext.getPackageName(), 0, UserHandle.of(userId));
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ controller = RoleControllerManager.createWithInitializedRemoteServiceComponentName(
+ ForegroundThread.getHandler(), context);
+ mControllers.put(userId, controller);
+ }
+ return controller;
+ }
+ }
+
+ @Nullable
+ private RemoteCallbackList<IOnRoleHoldersChangedListener> getListeners(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mListeners.get(userId);
+ }
+ }
+
+ @NonNull
+ private RemoteCallbackList<IOnRoleHoldersChangedListener> getOrCreateListeners(
+ @UserIdInt int userId) {
+ synchronized (mLock) {
+ RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = mListeners.get(userId);
+ if (listeners == null) {
+ listeners = new RemoteCallbackList<>();
+ mListeners.put(userId, listeners);
+ }
+ return listeners;
+ }
+ }
+
+ private void onRemoveUser(@UserIdInt int userId) {
+ RemoteCallbackList<IOnRoleHoldersChangedListener> listeners;
+ RoleUserState userState;
+ synchronized (mLock) {
+ mGrantDefaultRolesThrottledRunnables.remove(userId);
+ listeners = mListeners.get(userId);
+ mListeners.remove(userId);
+ mControllers.remove(userId);
+ userState = mUserStates.get(userId);
+ mUserStates.remove(userId);
+ }
+ if (listeners != null) {
+ listeners.kill();
+ }
+ if (userState != null) {
+ userState.destroy();
+ }
+ }
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+ mListenerHandler.post(() -> notifyRoleHoldersChanged(roleName, userId));
+ }
+
+ @WorkerThread
+ private void notifyRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId) {
+ RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
+ if (listeners != null) {
+ notifyRoleHoldersChangedForListeners(listeners, roleName, userId);
+ }
+
+ RemoteCallbackList<IOnRoleHoldersChangedListener> allUsersListeners = getListeners(
+ UserHandleCompat.USER_ALL);
+ if (allUsersListeners != null) {
+ notifyRoleHoldersChangedForListeners(allUsersListeners, roleName, userId);
+ }
+ }
+
+ @WorkerThread
+ private void notifyRoleHoldersChangedForListeners(
+ @NonNull RemoteCallbackList<IOnRoleHoldersChangedListener> listeners,
+ @NonNull String roleName, @UserIdInt int userId) {
+ int broadcastCount = listeners.beginBroadcast();
+ try {
+ for (int i = 0; i < broadcastCount; i++) {
+ IOnRoleHoldersChangedListener listener = listeners.getBroadcastItem(i);
+ try {
+ listener.onRoleHoldersChanged(roleName, userId);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Error calling OnRoleHoldersChangedListener", e);
+ }
+ }
+ } finally {
+ listeners.finishBroadcast();
+ }
+ }
+
+ private class Stub extends IRoleManager.Stub {
+
+ @Override
+ public boolean isRoleAvailable(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+
+ int userId = UserHandleCompat.getUserId(getCallingUid());
+ return getOrCreateUserState(userId).isRoleAvailable(roleName);
+ }
+
+ @Override
+ public boolean isRoleHeld(@NonNull String roleName, @NonNull String packageName) {
+ int callingUid = getCallingUid();
+ mAppOpsManager.checkPackage(callingUid, packageName);
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+
+ int userId = UserHandleCompat.getUserId(callingUid);
+ ArraySet<String> roleHolders = getOrCreateUserState(userId).getRoleHolders(roleName);
+ if (roleHolders == null) {
+ return false;
+ }
+ return roleHolders.contains(packageName);
+ }
+
+ @NonNull
+ @Override
+ public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {
+ if (!isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return Collections.emptyList();
+ }
+ enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "getRoleHoldersAsUser");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+
+ ArraySet<String> roleHolders = getOrCreateUserState(userId).getRoleHolders(roleName);
+ if (roleHolders == null) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(roleHolders);
+ }
+
+ @Override
+ public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
+ @NonNull RemoteCallback callback) {
+ if (!isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ enforceCrossUserPermission(userId, false, "addRoleHolderAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "addRoleHolderAsUser");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ getOrCreateController(userId).onAddRoleHolder(roleName, packageName, flags,
+ callback);
+ }
+
+ @Override
+ public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
+ @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
+ @NonNull RemoteCallback callback) {
+ if (!isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "removeRoleHolderAsUser");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ getOrCreateController(userId).onRemoveRoleHolder(roleName, packageName, flags,
+ callback);
+ }
+
+ @Override
+ public void clearRoleHoldersAsUser(@NonNull String roleName,
+ @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
+ @NonNull RemoteCallback callback) {
+ if (!isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "clearRoleHoldersAsUser");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ getOrCreateController(userId).onClearRoleHolders(roleName, flags, callback);
+ }
+
+ @Override
+ public void addOnRoleHoldersChangedListenerAsUser(
+ @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
+ if (userId != UserHandleCompat.USER_ALL && !isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ enforceCrossUserPermission(userId, true, "addOnRoleHoldersChangedListenerAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
+ "addOnRoleHoldersChangedListenerAsUser");
+
+ Objects.requireNonNull(listener, "listener cannot be null");
+
+ RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getOrCreateListeners(
+ userId);
+ listeners.register(listener);
+ }
+
+ @Override
+ public void removeOnRoleHoldersChangedListenerAsUser(
+ @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
+ if (userId != UserHandleCompat.USER_ALL && !isUserExistent(userId)) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ enforceCrossUserPermission(userId, true, "removeOnRoleHoldersChangedListenerAsUser");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.OBSERVE_ROLE_HOLDERS,
+ "removeOnRoleHoldersChangedListenerAsUser");
+
+ Objects.requireNonNull(listener, "listener cannot be null");
+
+ RemoteCallbackList<IOnRoleHoldersChangedListener> listeners = getListeners(userId);
+ if (listener == null) {
+ return;
+ }
+ listeners.unregister(listener);
+ }
+
+ @Override
+ public void setRoleNamesFromController(@NonNull List<String> roleNames) {
+ getContext().enforceCallingOrSelfPermission(
+ RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+ "setRoleNamesFromController");
+
+ Objects.requireNonNull(roleNames, "roleNames cannot be null");
+
+ int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
+ getOrCreateUserState(userId).setRoleNames(roleNames);
+ }
+
+ @Override
+ public boolean addRoleHolderFromController(@NonNull String roleName,
+ @NonNull String packageName) {
+ getContext().enforceCallingOrSelfPermission(
+ RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+ "addRoleHolderFromController");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+
+ int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
+ return getOrCreateUserState(userId).addRoleHolder(roleName, packageName);
+ }
+
+ @Override
+ public boolean removeRoleHolderFromController(@NonNull String roleName,
+ @NonNull String packageName) {
+ getContext().enforceCallingOrSelfPermission(
+ RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+ "removeRoleHolderFromController");
+
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+
+ int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
+ return getOrCreateUserState(userId).removeRoleHolder(roleName, packageName);
+ }
+
+ @Override
+ public List<String> getHeldRolesFromController(@NonNull String packageName) {
+ getContext().enforceCallingOrSelfPermission(
+ RoleManager.PERMISSION_MANAGE_ROLES_FROM_CONTROLLER,
+ "getRolesHeldFromController");
+
+ Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
+
+ int userId = UserHandleCompat.getUserId(Binder.getCallingUid());
+ return getOrCreateUserState(userId).getHeldRoles(packageName);
+ }
+
+ private boolean isUserExistent(@UserIdInt int userId) {
+ return mUserManager.getUserHandles(true).contains(UserHandle.of(userId));
+ }
+
+ private void enforceCrossUserPermission(@UserIdInt int userId, boolean allowAll,
+ @NonNull String message) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandleCompat.getUserId(callingUid);
+ if (userId == callingUserId) {
+ return;
+ }
+ Preconditions.checkArgument(userId >= UserHandleCompat.USER_SYSTEM
+ || (allowAll && userId == UserHandleCompat.USER_ALL), "Invalid user " + userId);
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+ if (callingUid == Process.SHELL_UID && userId >= UserHandleCompat.USER_SYSTEM) {
+ if (mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserHandle.of(userId))) {
+ throw new SecurityException("Shell does not have permission to access user "
+ + userId);
+ }
+ }
+ }
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return new RoleShellCommand(this).exec(this, in.getFileDescriptor(),
+ out.getFileDescriptor(), err.getFileDescriptor(), args);
+ }
+
+ @Nullable
+ @Override
+ public String getBrowserRoleHolder(@UserIdInt int userId) {
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandleCompat.getUserId(callingUid) != userId) {
+ getContext().enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+ if (isInstantApp(callingUid)) {
+ return null;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(getRoleHoldersAsUser(RoleManager.ROLE_BROWSER,
+ userId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private boolean isInstantApp(int uid) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final UserHandle user = UserHandle.getUserHandleForUid(uid);
+ final Context userContext = getContext().createContextAsUser(user, 0);
+ final PackageManager userPackageManager = userContext.getPackageManager();
+ // Instant apps can not have shared UID, so it's safe to check only the first
+ // package name here.
+ final String packageName = ArrayUtils.firstOrNull(
+ userPackageManager.getPackagesForUid(uid));
+ if (packageName == null) {
+ return false;
+ }
+ return userPackageManager.isInstantApp(packageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setBrowserRoleHolder(@Nullable String packageName, @UserIdInt int userId) {
+ final Context context = getContext();
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+ if (UserHandleCompat.getUserId(Binder.getCallingUid()) != userId) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+
+ if (!isUserExistent(userId)) {
+ return false;
+ }
+
+ final AndroidFuture<Void> future = new AndroidFuture<>();
+ final RemoteCallback callback = new RemoteCallback(result -> {
+ boolean successful = result != null;
+ if (successful) {
+ future.complete(null);
+ } else {
+ future.completeExceptionally(new RuntimeException());
+ }
+ });
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (packageName != null) {
+ addRoleHolderAsUser(RoleManager.ROLE_BROWSER, packageName, 0, userId, callback);
+ } else {
+ clearRoleHoldersAsUser(RoleManager.ROLE_BROWSER, 0, userId, callback);
+ }
+ try {
+ future.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Exception while setting default browser: " + packageName, e);
+ return false;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ return true;
+ }
+
+ @Override
+ public String getSmsRoleHolder(int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return CollectionUtils.firstOrNull(getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+ userId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
+ @Nullable String[] args) {
+ if (!checkDumpPermission("role", fout)) {
+ return;
+ }
+
+ boolean dumpAsProto = args != null && ArrayUtils.contains(args, "--proto");
+ DualDumpOutputStream dumpOutputStream;
+ if (dumpAsProto) {
+ dumpOutputStream = new DualDumpOutputStream(new ProtoOutputStream(
+ new FileOutputStream(fd)));
+ } else {
+ fout.println("ROLE STATE (dumpsys role):");
+ dumpOutputStream = new DualDumpOutputStream(new IndentingPrintWriter(fout, " "));
+ }
+
+ synchronized (mLock) {
+ final int userStatesSize = mUserStates.size();
+ for (int i = 0; i < userStatesSize; i++) {
+ final RoleUserState userState = mUserStates.valueAt(i);
+
+ userState.dump(dumpOutputStream, "user_states",
+ RoleServiceDumpProto.USER_STATES);
+ }
+ }
+
+ dumpOutputStream.flush();
+ }
+
+ private boolean checkDumpPermission(@NonNull String serviceName,
+ @NonNull PrintWriter writer) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ writer.println("Permission Denial: can't dump " + serviceName + " from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " due to missing " + android.Manifest.permission.DUMP + " permission");
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private class Local implements RoleManagerLocal {
+ @NonNull
+ @Override
+ public Map<String, Set<String>> getRolesAndHolders(@UserIdInt int userId) {
+ // Convert ArrayMap<String, ArraySet<String>> to Map<String, Set<String>> for the API.
+ //noinspection unchecked
+ return (Map<String, Set<String>>) (Map<String, ?>)
+ getOrCreateUserState(userId).getRolesAndHolders();
+ }
+ }
+}
diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java
new file mode 100644
index 000000000..03b7c76d2
--- /dev/null
+++ b/service/java/com/android/role/RoleShellCommand.java
@@ -0,0 +1,151 @@
+/*
+ * 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 com.android.role;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.role.IRoleManager;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.permission.compat.UserHandleCompat;
+
+import java.io.PrintWriter;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+class RoleShellCommand extends BasicShellCommandHandler {
+ @NonNull
+ private final IRoleManager mRoleManager;
+
+ RoleShellCommand(@NonNull IRoleManager roleManager) {
+ mRoleManager = roleManager;
+ }
+
+ private class CallbackFuture extends CompletableFuture<Void> {
+ @NonNull
+ public RemoteCallback createCallback() {
+ return new RemoteCallback(result -> {
+ boolean successful = result != null;
+ if (successful) {
+ complete(null);
+ } else {
+ completeExceptionally(new RuntimeException("Failed"));
+ }
+ });
+ }
+
+ public int waitForResult() {
+ try {
+ get(5, TimeUnit.SECONDS);
+ return 0;
+ } catch (Exception e) {
+ getErrPrintWriter().println("Error: see logcat for details.\n" + e);
+ return -1;
+ }
+ }
+ }
+
+ @Override
+ public int onCommand(@Nullable String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "add-role-holder":
+ return runAddRoleHolder();
+ case "remove-role-holder":
+ return runRemoveRoleHolder();
+ case "clear-role-holders":
+ return runClearRoleHolders();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int getUserIdMaybe() {
+ int userId = UserHandleCompat.USER_SYSTEM;
+ String option = getNextOption();
+ if (option != null && option.equals("--user")) {
+ userId = Integer.parseInt(getNextArgRequired());
+ }
+ return userId;
+ }
+
+ private int getFlagsMaybe() {
+ String flags = getNextArg();
+ if (flags == null) {
+ return 0;
+ }
+ return Integer.parseInt(flags);
+ }
+
+ private int runAddRoleHolder() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+ String packageName = getNextArgRequired();
+ int flags = getFlagsMaybe();
+
+ CallbackFuture future = new CallbackFuture();
+ mRoleManager.addRoleHolderAsUser(roleName, packageName, flags, userId,
+ future.createCallback());
+ return future.waitForResult();
+ }
+
+ private int runRemoveRoleHolder() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+ String packageName = getNextArgRequired();
+ int flags = getFlagsMaybe();
+
+ CallbackFuture future = new CallbackFuture();
+ mRoleManager.removeRoleHolderAsUser(roleName, packageName, flags, userId,
+ future.createCallback());
+ return future.waitForResult();
+ }
+
+ private int runClearRoleHolders() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+ int flags = getFlagsMaybe();
+
+ CallbackFuture future = new CallbackFuture();
+ mRoleManager.clearRoleHoldersAsUser(roleName, flags, userId, future.createCallback());
+ return future.waitForResult();
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Role (role) commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ pw.println();
+ pw.println(" add-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
+ pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
+ pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]");
+ pw.println();
+ }
+}
diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java
new file mode 100644
index 000000000..78d8d15bb
--- /dev/null
+++ b/service/java/com/android/role/RoleUserState.java
@@ -0,0 +1,476 @@
+/*
+ * 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 com.android.role;
+
+import android.annotation.CheckResult;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.permission.util.BackgroundThread;
+import com.android.permission.util.CollectionUtils;
+import com.android.role.persistence.RolesPersistence;
+import com.android.role.persistence.RolesState;
+import com.android.server.role.RoleServicePlatformHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Stores the state of roles for a user.
+ */
+class RoleUserState {
+ private static final String LOG_TAG = RoleUserState.class.getSimpleName();
+
+ public static final int VERSION_UNDEFINED = -1;
+
+ private static final long WRITE_DELAY_MILLIS = 200;
+
+ private final RolesPersistence mPersistence = RolesPersistence.createInstance();
+
+ @UserIdInt
+ private final int mUserId;
+
+ @NonNull
+ private final RoleServicePlatformHelper mPlatformHelper;
+
+ @NonNull
+ private final Callback mCallback;
+
+ @NonNull
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private int mVersion = VERSION_UNDEFINED;
+
+ @GuardedBy("mLock")
+ @Nullable
+ private String mPackagesHash;
+
+ /**
+ * Maps role names to its holders' package names. The values should never be null.
+ */
+ @GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private boolean mWriteScheduled;
+
+ @GuardedBy("mLock")
+ private boolean mDestroyed;
+
+ @NonNull
+ private final Handler mWriteHandler = new Handler(BackgroundThread.get().getLooper());
+
+ /**
+ * Create a new user state, and read its state from disk if previously persisted.
+ *
+ * @param userId the user id for this user state
+ * @param platformHelper the platform helper
+ * @param callback the callback for this user state
+ */
+ public RoleUserState(@UserIdInt int userId, @NonNull RoleServicePlatformHelper platformHelper,
+ @NonNull Callback callback) {
+ mUserId = userId;
+ mPlatformHelper = platformHelper;
+ mCallback = callback;
+
+ readFile();
+ }
+
+ /**
+ * Get the version of this user state.
+ */
+ public int getVersion() {
+ synchronized (mLock) {
+ return mVersion;
+ }
+ }
+
+ /**
+ * Set the version of this user state.
+ *
+ * @param version the version to set
+ */
+ public void setVersion(int version) {
+ synchronized (mLock) {
+ if (mVersion == version) {
+ return;
+ }
+ mVersion = version;
+ scheduleWriteFileLocked();
+ }
+ }
+
+ /**
+ * Get the hash representing the state of packages during the last time initial grants was run.
+ *
+ * @return the hash representing the state of packages
+ */
+ @Nullable
+ public String getPackagesHash() {
+ synchronized (mLock) {
+ return mPackagesHash;
+ }
+ }
+
+ /**
+ * Set the hash representing the state of packages during the last time initial grants was run.
+ *
+ * @param packagesHash the hash representing the state of packages
+ */
+ public void setPackagesHash(@Nullable String packagesHash) {
+ synchronized (mLock) {
+ if (Objects.equals(mPackagesHash, packagesHash)) {
+ return;
+ }
+ mPackagesHash = packagesHash;
+ scheduleWriteFileLocked();
+ }
+ }
+
+ /**
+ * Get whether the role is available.
+ *
+ * @param roleName the name of the role to get the holders for
+ *
+ * @return whether the role is available
+ */
+ public boolean isRoleAvailable(@NonNull String roleName) {
+ synchronized (mLock) {
+ return mRoles.containsKey(roleName);
+ }
+ }
+
+ /**
+ * Get the holders of a role.
+ *
+ * @param roleName the name of the role to query for
+ *
+ * @return the set of role holders, or {@code null} if and only if the role is not found
+ */
+ @Nullable
+ public ArraySet<String> getRoleHolders(@NonNull String roleName) {
+ synchronized (mLock) {
+ ArraySet<String> packageNames = mRoles.get(roleName);
+ if (packageNames == null) {
+ return null;
+ }
+ return new ArraySet<>(packageNames);
+ }
+ }
+
+ /**
+ * Adds the given role, effectively marking it as {@link #isRoleAvailable available}
+ *
+ * @param roleName the name of the role
+ *
+ * @return whether any changes were made
+ */
+ public boolean addRoleName(@NonNull String roleName) {
+ synchronized (mLock) {
+ if (!mRoles.containsKey(roleName)) {
+ mRoles.put(roleName, new ArraySet<>());
+ Log.i(LOG_TAG, "Added new role: " + roleName);
+ scheduleWriteFileLocked();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Set the names of all available roles.
+ *
+ * @param roleNames the names of all the available roles
+ */
+ public void setRoleNames(@NonNull List<String> roleNames) {
+ synchronized (mLock) {
+ boolean changed = false;
+
+ for (int i = mRoles.size() - 1; i >= 0; i--) {
+ String roleName = mRoles.keyAt(i);
+
+ if (!roleNames.contains(roleName)) {
+ ArraySet<String> packageNames = mRoles.valueAt(i);
+ if (!packageNames.isEmpty()) {
+ Log.e(LOG_TAG, "Holders of a removed role should have been cleaned up,"
+ + " role: " + roleName + ", holders: " + packageNames);
+ }
+ mRoles.removeAt(i);
+ changed = true;
+ }
+ }
+
+ int roleNamesSize = roleNames.size();
+ for (int i = 0; i < roleNamesSize; i++) {
+ changed |= addRoleName(roleNames.get(i));
+ }
+
+ if (changed) {
+ scheduleWriteFileLocked();
+ }
+ }
+ }
+
+ /**
+ * Add a holder to a role.
+ *
+ * @param roleName the name of the role to add the holder to
+ * @param packageName the package name of the new holder
+ *
+ * @return {@code false} if and only if the role is not found
+ */
+ @CheckResult
+ public boolean addRoleHolder(@NonNull String roleName, @NonNull String packageName) {
+ boolean changed;
+
+ synchronized (mLock) {
+ ArraySet<String> roleHolders = mRoles.get(roleName);
+ if (roleHolders == null) {
+ Log.e(LOG_TAG, "Cannot add role holder for unknown role, role: " + roleName
+ + ", package: " + packageName);
+ return false;
+ }
+ changed = roleHolders.add(packageName);
+ if (changed) {
+ scheduleWriteFileLocked();
+ }
+ }
+
+ if (changed) {
+ mCallback.onRoleHoldersChanged(roleName, mUserId);
+ }
+ return true;
+ }
+
+ /**
+ * Remove a holder from a role.
+ *
+ * @param roleName the name of the role to remove the holder from
+ * @param packageName the package name of the holder to remove
+ *
+ * @return {@code false} if and only if the role is not found
+ */
+ @CheckResult
+ public boolean removeRoleHolder(@NonNull String roleName, @NonNull String packageName) {
+ boolean changed;
+
+ synchronized (mLock) {
+ ArraySet<String> roleHolders = mRoles.get(roleName);
+ if (roleHolders == null) {
+ Log.e(LOG_TAG, "Cannot remove role holder for unknown role, role: " + roleName
+ + ", package: " + packageName);
+ return false;
+ }
+
+ changed = roleHolders.remove(packageName);
+ if (changed) {
+ scheduleWriteFileLocked();
+ }
+ }
+
+ if (changed) {
+ mCallback.onRoleHoldersChanged(roleName, mUserId);
+ }
+ return true;
+ }
+
+ /**
+ * @see android.app.role.RoleManager#getHeldRolesFromController
+ */
+ @NonNull
+ public List<String> getHeldRoles(@NonNull String packageName) {
+ synchronized (mLock) {
+ List<String> roleNames = new ArrayList<>();
+ int size = mRoles.size();
+ for (int i = 0; i < size; i++) {
+ if (mRoles.valueAt(i).contains(packageName)) {
+ roleNames.add(mRoles.keyAt(i));
+ }
+ }
+ return roleNames;
+ }
+ }
+
+ /**
+ * Schedule writing the state to file.
+ */
+ @GuardedBy("mLock")
+ private void scheduleWriteFileLocked() {
+ if (mDestroyed) {
+ return;
+ }
+
+ if (!mWriteScheduled) {
+ mWriteHandler.postDelayed(this::writeFile, WRITE_DELAY_MILLIS);
+ mWriteScheduled = true;
+ }
+ }
+
+ @WorkerThread
+ private void writeFile() {
+ RolesState roles;
+ synchronized (mLock) {
+ if (mDestroyed) {
+ return;
+ }
+
+ mWriteScheduled = false;
+
+ roles = new RolesState(mVersion, mPackagesHash,
+ (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked());
+ }
+
+ mPersistence.writeForUser(roles, UserHandle.of(mUserId));
+ }
+
+ private void readFile() {
+ synchronized (mLock) {
+ RolesState roleState = mPersistence.readForUser(UserHandle.of(mUserId));
+
+ Map<String, Set<String>> roles;
+ if (roleState != null) {
+ mVersion = roleState.getVersion();
+ mPackagesHash = roleState.getPackagesHash();
+ roles = roleState.getRoles();
+ } else {
+ roles = mPlatformHelper.getLegacyRoleState(mUserId);
+ }
+ mRoles.clear();
+ for (Map.Entry<String, Set<String>> entry : roles.entrySet()) {
+ String roleName = entry.getKey();
+ ArraySet<String> roleHolders = new ArraySet<>(entry.getValue());
+ mRoles.put(roleName, roleHolders);
+ }
+
+ if (roleState == null) {
+ scheduleWriteFileLocked();
+ }
+ }
+ }
+
+ /**
+ * Dump this user state.
+ *
+ * @param dumpOutputStream the output stream to dump to
+ */
+ public void dump(@NonNull DualDumpOutputStream dumpOutputStream, @NonNull String fieldName,
+ long fieldId) {
+ int version;
+ String packagesHash;
+ ArrayMap<String, ArraySet<String>> roles;
+ synchronized (mLock) {
+ version = mVersion;
+ packagesHash = mPackagesHash;
+ roles = snapshotRolesLocked();
+ }
+
+ long fieldToken = dumpOutputStream.start(fieldName, fieldId);
+ dumpOutputStream.write("user_id", RoleUserStateProto.USER_ID, mUserId);
+ dumpOutputStream.write("version", RoleUserStateProto.VERSION, version);
+ dumpOutputStream.write("packages_hash", RoleUserStateProto.PACKAGES_HASH, packagesHash);
+
+ int rolesSize = roles.size();
+ for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
+ String roleName = roles.keyAt(rolesIndex);
+ ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
+
+ long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
+ dumpOutputStream.write("name", RoleProto.NAME, roleName);
+
+ int roleHoldersSize = roleHolders.size();
+ for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
+ String roleHolder = roleHolders.valueAt(roleHoldersIndex);
+
+ dumpOutputStream.write("holders", RoleProto.HOLDERS, roleHolder);
+ }
+
+ dumpOutputStream.end(rolesToken);
+ }
+
+ dumpOutputStream.end(fieldToken);
+ }
+
+ /**
+ * Get the roles and their holders.
+ *
+ * @return A copy of the roles and their holders
+ */
+ @NonNull
+ public ArrayMap<String, ArraySet<String>> getRolesAndHolders() {
+ synchronized (mLock) {
+ return snapshotRolesLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, ArraySet<String>> snapshotRolesLocked() {
+ ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>();
+ for (int i = 0, size = CollectionUtils.size(mRoles); i < size; ++i) {
+ String roleName = mRoles.keyAt(i);
+ ArraySet<String> roleHolders = mRoles.valueAt(i);
+
+ roleHolders = new ArraySet<>(roleHolders);
+ roles.put(roleName, roleHolders);
+ }
+ return roles;
+ }
+
+ /**
+ * Destroy this user state and delete the corresponding file. Any pending writes to the file
+ * will be cancelled, and any future interaction with this state will throw an exception.
+ */
+ public void destroy() {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ throw new IllegalStateException("This RoleUserState has already been destroyed");
+ }
+ mWriteHandler.removeCallbacksAndMessages(null);
+ mPersistence.deleteForUser(UserHandle.of(mUserId));
+ mDestroyed = true;
+ }
+ }
+
+ /**
+ * Callback for a user state.
+ */
+ public interface Callback {
+
+ /**
+ * Called when the holders of roles are changed.
+ *
+ * @param roleName the name of the role whose holders are changed
+ * @param userId the user id for this role holder change
+ */
+ void onRoleHoldersChanged(@NonNull String roleName, @UserIdInt int userId);
+ }
+}
diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING
new file mode 100644
index 000000000..0d7bc1476
--- /dev/null
+++ b/service/java/com/android/role/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsStatsdHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.cts.statsd.atom.UidAtomTests#testRoleHolder"
+ }
+ ]
+ },
+ {
+ "name": "CtsRoleTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/service/java/com/android/role/package-info.java b/service/java/com/android/role/package-info.java
new file mode 100644
index 000000000..8b5b25161
--- /dev/null
+++ b/service/java/com/android/role/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.role;
diff --git a/service/proto/com/android/role/roleservice.proto b/service/proto/com/android/role/roleservice.proto
new file mode 100644
index 000000000..79c422992
--- /dev/null
+++ b/service/proto/com/android/role/roleservice.proto
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.role;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+message RoleServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // List of per-user states for all users.
+ repeated RoleUserStateProto user_states = 1;
+}
+
+message RoleUserStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The user id of this state.
+ optional int32 user_id = 1;
+
+ // The version of this state.
+ optional int32 version = 2;
+
+ // The hash of packages for this state.
+ optional string packages_hash = 3;
+
+ // The set of roles in this state.
+ repeated RoleProto roles = 4;
+}
+
+message RoleProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The name of this role, e.g. "android.app.role.DIALER".
+ optional string name = 1;
+
+ // The package names of the holders of this role.
+ repeated string holders = 2;
+}