/* * 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.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Build; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.permission.flags.Flags; import android.util.ArrayMap; import android.util.SparseArray; import androidx.annotation.RequiresApi; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import com.android.modules.utils.build.SdkLevel; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; 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. *

* A role is a unique name within the system associated with certain privileges. The list of * available roles might change with a system app update, so apps should not make assumption about * the availability of roles. Instead, they should always query if the role is available using * {@link #isRoleAvailable(String)} before trying to do anything with it. Some predefined role names * are available as constants in this class, and a list of possibly available roles can be found in * the AndroidX Role * library. *

* 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)}. *

* 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 { /** * 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. *

* 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"; /** * The name of the notes role. * * @see Intent#ACTION_CREATE_NOTE * @see Intent#EXTRA_USE_STYLUS_MODE */ public static final String ROLE_NOTES = "android.app.role.NOTES"; /** * The name of the Wallet role. * * @see android.nfc.cardemulation.CardEmulation */ @FlaggedApi(Flags.FLAG_WALLET_ROLE_ENABLED) @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) public static final String ROLE_WALLET = "android.app.role.WALLET"; /** * The name of the system wellbeing role. * * @hide */ @SystemApi public static final String ROLE_SYSTEM_WELLBEING = "android.app.role.SYSTEM_WELLBEING"; /** * The name of the system supervision role. * * @hide */ @SystemApi public static final String ROLE_SYSTEM_SUPERVISION = "android.app.role.SYSTEM_SUPERVISION"; /** * The name of the system activity recognizer role. * * @hide */ @SystemApi public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER"; /** * The name of the device policy management role. * * @hide */ @SystemApi public static final String ROLE_DEVICE_POLICY_MANAGEMENT = "android.app.role.DEVICE_POLICY_MANAGEMENT"; /** * The name of the financed device kiosk role. * * A financed device is a device purchased through a creditor and typically paid back under an * installment plan. * The creditor has the ability to lock a financed device in case of payment default. * * @hide */ @SystemApi public static final String ROLE_FINANCED_DEVICE_KIOSK = "android.app.role.FINANCED_DEVICE_KIOSK"; /** * The name of the system call streaming role. * * @hide */ @SystemApi public static final String ROLE_SYSTEM_CALL_STREAMING = "android.app.role.SYSTEM_CALL_STREAMING"; /** * @hide */ @IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP }) @Retention(RetentionPolicy.SOURCE) 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; /** * For apps targeting Android V and above, several methods are now user-handle-aware, which * means they use the user contained within the context. For apps targeting an SDK version * below this, the user of the calling process will be used. * * @hide */ @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public static final long ROLE_MANAGER_USER_HANDLE_AWARE = 303742236L; /** * 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> 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. *

* 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 */ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public boolean isRoleAvailable(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); UserHandle user = getContextUserIfAppropriate(); try { return mService.isRoleAvailableAsUser(roleName, user.getIdentifier()); } 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 */ @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public boolean isRoleHeld(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); UserHandle user = getContextUserIfAppropriate(); try { return mService.isRoleHeldAsUser(roleName, mContext.getPackageName(), user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Get package names of the applications holding the role. *

* Note: 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 @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public List getRoleHolders(@NonNull String roleName) { return getRoleHoldersAsUser(roleName, getContextUserIfAppropriate()); } /** * Get package names of the applications holding the role. *

* Note: 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 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. *

* Note: 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 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. *

* Note: 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 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. *

* Note: 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 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(); } } /** * Get package name of the application holding the role for a default application. *

* Only roles describing default applications can be used with this method. They can have * at most one holder. * * @param roleName the name of the default application role to get * * @return a package name of the role holder or {@code null} if not set. * * @see #setDefaultApplication(String, String, int, Executor, Consumer) * * @hide */ @Nullable @RequiresPermission(Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @UserHandleAware @SystemApi public String getDefaultApplication(@NonNull String roleName) { Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); try { return mService.getDefaultApplicationAsUser( roleName, mContext.getUser().getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Set a specific application as the default application. *

* Only roles describing default applications can be used with this method. They can have * at most one holder. * * @param roleName the name of the default application role to set the role holder for * @param packageName the package name of the application to set as the default application, * or {@code null} to unset. * @param flags optional behavior flags * @param executor the {@code Executor} to run the callback on. * @param callback the callback for whether this call is successful * * @see #getDefaultApplication(String) * * @hide */ @RequiresPermission(Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @UserHandleAware @SystemApi public void setDefaultApplication(@NonNull String roleName, @Nullable String packageName, @ManageHoldersFlags int flags, @CallbackExecutor @NonNull Executor executor, @NonNull Consumer callback) { // Prior to Android V some devices might require the "packageName" to be non-null. Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); Objects.requireNonNull(executor, "executor cannot be null"); Objects.requireNonNull(callback, "callback cannot be null"); try { mService.setDefaultApplicationAsUser(roleName, packageName, flags, mContext.getUser().getIdentifier(), createRemoteCallback(executor, callback)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @NonNull private static RemoteCallback createRemoteCallback(@NonNull Executor executor, @NonNull Consumer 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 *

* Note: 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) @SuppressLint("SamShouldBeLast") // TODO(b/190240500): remove this @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 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 *

* Note: 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) @SuppressLint("SamShouldBeLast") // TODO(b/190240500): remove this @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 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); } } } /** * Check whether role qualifications should be bypassed. *

* Only the shell is allowed to do this, the qualification for the shell role itself cannot be * bypassed, and each role needs to explicitly allow bypassing qualification in its definition. * The bypass state will not be persisted across reboot. * * @return whether role qualification should be bypassed * * @hide */ @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @SystemApi public boolean isBypassingRoleQualification() { try { return mService.isBypassingRoleQualification(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Set whether role qualifications should be bypassed. *

* Only the shell is allowed to do this, the qualification for the shell role itself cannot be * bypassed, and each role needs to explicitly allow bypassing qualification in its definition. * The bypass state will not be persisted across reboot. * * @param bypassRoleQualification whether role qualification should be bypassed * * @hide */ @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(Manifest.permission.BYPASS_ROLE_QUALIFICATION) @SystemApi public void setBypassingRoleQualification(boolean bypassRoleQualification) { try { mService.setBypassingRoleQualification(bypassRoleQualification); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Check whether role currently enables fallback to default holder. *

* This is based on the "None" holder being actively selected, in which case don't fallback. * * @param roleName the name of the role being queried * * @return whether fallback is enabled for the provided role * * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) @UserHandleAware @SystemApi public boolean isRoleFallbackEnabled(@NonNull String roleName) { try { return mService.isRoleFallbackEnabledAsUser(roleName, mContext.getUser().getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Set whether role should fallback to a default role holder. * * @param roleName the name of the role being queried. * @param fallbackEnabled whether to enable fallback holders for this role. * * @hide */ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) @UserHandleAware @SystemApi public void setRoleFallbackEnabled(@NonNull String roleName, boolean fallbackEnabled) { try { mService.setRoleFallbackEnabledAsUser(roleName, fallbackEnabled, mContext.getUser().getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Set the names of all the available roles. Should only be called from * {@link android.app.role.RoleControllerService}. *

* Note: 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 @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public void setRoleNamesFromController(@NonNull List roleNames) { Objects.requireNonNull(roleNames, "roleNames cannot be null"); UserHandle user = getContextUserIfAppropriate(); try { mService.setRoleNamesFromControllerAsUser(roleNames, user.getIdentifier()); } 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}. *

* Note: 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 @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) 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"); UserHandle user = getContextUserIfAppropriate(); try { return mService.addRoleHolderFromControllerAsUser(roleName, packageName, user.getIdentifier()); } 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}. *

* Note: 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 @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) 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"); UserHandle user = getContextUserIfAppropriate(); try { return mService.removeRoleHolderFromControllerAsUser(roleName, packageName, user.getIdentifier()); } 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 @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) public List getHeldRolesFromController(@NonNull String packageName) { Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); UserHandle user = getContextUserIfAppropriate(); try { return mService.getHeldRolesFromControllerAsUser(packageName, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private UserHandle getContextUserIfAppropriate() { return CompatChanges.isChangeEnabled(ROLE_MANAGER_USER_HANDLE_AWARE) ? mContext.getUser() : Process.myUserHandle(); } /** * 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 */ @RequiresApi(Build.VERSION_CODES.S) @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 */ @RequiresApi(Build.VERSION_CODES.S) @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 */ @RequiresApi(Build.VERSION_CODES.S) @Nullable @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public String getSmsRoleHolder(@UserIdInt int userId) { try { return mService.getSmsRoleHolder(userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Allows getting the role holder for {@link #ROLE_EMERGENCY} without requiring * {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}. * * @param userId the user ID to get the default emergency package for * @return the package name of the default emergency app, or {@code null} if none * * @hide */ @FlaggedApi(Flags.FLAG_GET_EMERGENCY_ROLE_HOLDER_API_ENABLED) @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @Nullable @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public String getEmergencyRoleHolder(@UserIdInt int userId) { try { return mService.getEmergencyRoleHolder(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 */ @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @SystemApi public void isRoleVisible(@NonNull String roleName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) { int userId = getContextUserIfAppropriate().getIdentifier(); boolean visible; try { visible = mService.isRoleVisibleAsUser(roleName, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } executor.execute(() -> { final long token = Binder.clearCallingIdentity(); try { callback.accept(visible); } finally { Binder.restoreCallingIdentity(token); } }); } else { 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 */ @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @SystemApi public void isApplicationVisibleForRole(@NonNull String roleName, @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) { int userId = getContextUserIfAppropriate().getIdentifier(); boolean visible; try { visible = mService.isApplicationVisibleForRoleAsUser(roleName, packageName, userId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } executor.execute(() -> { final long token = Binder.clearCallingIdentity(); try { callback.accept(visible); } finally { Binder.restoreCallingIdentity(token); } }); } else { 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); } } } }