blob: 9ff16db0fc4356eca09fc6f2e1c2ae7d4af7a069 [file] [log] [blame]
/*
* 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.controller.model;
import android.app.ActivityManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.util.CollectionUtils;
import com.android.role.controller.util.PackageUtils;
import com.android.role.controller.util.RoleManagerCompat;
import com.android.role.controller.util.UserUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Specifies a role and its properties.
* <p>
* A role is a unique name within the system associated with certain privileges. There can be
* multiple applications qualifying for a role, but only a subset of them can become role holders.
* To qualify for a role, an application must meet certain requirements, including defining certain
* components in its manifest. Then the application will need user consent to become the role
* holder.
* <p>
* Upon becoming a role holder, the application may be granted certain permissions, have certain
* app ops set to certain modes and certain {@code Activity} components configured as preferred for
* certain {@code Intent} actions. When an application loses its role, these privileges will also be
* revoked.
*
* @see android.app.role.RoleManager
*/
public class Role {
private static final String LOG_TAG = Role.class.getSimpleName();
private static final boolean DEBUG = false;
private static final String PACKAGE_NAME_ANDROID_SYSTEM = "android";
private static final String DEFAULT_HOLDER_SEPARATOR = ";";
private static final String CERTIFICATE_SEPARATOR = ":";
/**
* The name of this role. Must be unique.
*/
@NonNull
private final String mName;
/**
* Whether this role allows bypassing role holder qualification.
*/
private final boolean mAllowBypassingQualification;
/**
* The behavior of this role.
*/
@Nullable
private final RoleBehavior mBehavior;
@Nullable
private final String mDefaultHoldersResourceName;
/**
* The string resource for the description of this role.
*/
@StringRes
private final int mDescriptionResource;
/**
* Whether this role is exclusive, i.e. allows at most one holder.
*/
private final boolean mExclusive;
/**
* Whether this role should fall back to the default holder.
*/
private final boolean mFallBackToDefaultHolder;
/**
* The string resource for the label of this role.
*/
@StringRes
private final int mLabelResource;
/**
* The maximum SDK version for this role to be available.
*/
private final int mMaxSdkVersion;
/**
* The minimum SDK version for this role to be available.
*/
private final int mMinSdkVersion;
/**
* Whether this role should override user's choice about privileges when granting.
*/
private final boolean mOverrideUserWhenGranting;
/**
* The string resource for the request description of this role, shown below the selected app in
* the request role dialog.
*/
@StringRes
private final int mRequestDescriptionResource;
/**
* The string resource for the request title of this role, shown as the title of the request
* role dialog.
*/
@StringRes
private final int mRequestTitleResource;
/**
* Whether this role is requestable by applications with
* {@link android.app.role.RoleManager#createRequestRoleIntent(String)}.
*/
private final boolean mRequestable;
/**
* The string resource for search keywords of this role, in addition to the label of this role,
* if it's non-zero.
*/
@StringRes
private final int mSearchKeywordsResource;
/**
* The string resource for the short label of this role, currently used when in a list of roles.
*/
@StringRes
private final int mShortLabelResource;
/**
* Whether the UI for this role will show the "None" item. Only valid if this role is
* {@link #mExclusive exclusive}, and {@link #getFallbackHolder(Context)} should also return
* empty to allow actually selecting "None".
*/
private final boolean mShowNone;
/**
* Whether this role is static, i.e. the role will always be assigned to its default holders.
*/
private final boolean mStatic;
/**
* Whether this role only accepts system apps as its holders.
*/
private final boolean mSystemOnly;
/**
* Whether this role is visible to user.
*/
private final boolean mVisible;
/**
* The required components for an application to qualify for this role.
*/
@NonNull
private final List<RequiredComponent> mRequiredComponents;
/**
* The permissions to be granted by this role.
*/
@NonNull
private final List<Permission> mPermissions;
/**
* The app op permissions to be granted by this role.
*/
@NonNull
private final List<Permission> mAppOpPermissions;
/**
* The app ops to be set to allowed by this role.
*/
@NonNull
private final List<AppOp> mAppOps;
/**
* The set of preferred {@code Activity} configurations to be configured by this role.
*/
@NonNull
private final List<PreferredActivity> mPreferredActivities;
@Nullable
private final String mUiBehaviorName;
public Role(@NonNull String name, boolean allowBypassingQualification,
@Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName,
@StringRes int descriptionResource, boolean exclusive, boolean fallBackToDefaultHolder,
@StringRes int labelResource, int maxSdkVersion, int minSdkVersion,
boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource,
@StringRes int requestTitleResource, boolean requestable,
@StringRes int searchKeywordsResource, @StringRes int shortLabelResource,
boolean showNone, boolean statik, boolean systemOnly, boolean visible,
@NonNull List<RequiredComponent> requiredComponents,
@NonNull List<Permission> permissions, @NonNull List<Permission> appOpPermissions,
@NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities,
@Nullable String uiBehaviorName) {
mName = name;
mAllowBypassingQualification = allowBypassingQualification;
mBehavior = behavior;
mDefaultHoldersResourceName = defaultHoldersResourceName;
mDescriptionResource = descriptionResource;
mExclusive = exclusive;
mFallBackToDefaultHolder = fallBackToDefaultHolder;
mLabelResource = labelResource;
mMaxSdkVersion = maxSdkVersion;
mMinSdkVersion = minSdkVersion;
mOverrideUserWhenGranting = overrideUserWhenGranting;
mRequestDescriptionResource = requestDescriptionResource;
mRequestTitleResource = requestTitleResource;
mRequestable = requestable;
mSearchKeywordsResource = searchKeywordsResource;
mShortLabelResource = shortLabelResource;
mShowNone = showNone;
mStatic = statik;
mSystemOnly = systemOnly;
mVisible = visible;
mRequiredComponents = requiredComponents;
mPermissions = permissions;
mAppOpPermissions = appOpPermissions;
mAppOps = appOps;
mPreferredActivities = preferredActivities;
mUiBehaviorName = uiBehaviorName;
}
@NonNull
public String getName() {
return mName;
}
@Nullable
public RoleBehavior getBehavior() {
return mBehavior;
}
@StringRes
public int getDescriptionResource() {
return mDescriptionResource;
}
public boolean isExclusive() {
return mExclusive;
}
@StringRes
public int getLabelResource() {
return mLabelResource;
}
@StringRes
public int getRequestDescriptionResource() {
return mRequestDescriptionResource;
}
@StringRes
public int getRequestTitleResource() {
return mRequestTitleResource;
}
public boolean isRequestable() {
return mRequestable;
}
@StringRes
public int getSearchKeywordsResource() {
return mSearchKeywordsResource;
}
@StringRes
public int getShortLabelResource() {
return mShortLabelResource;
}
/**
* @see #mOverrideUserWhenGranting
*/
public boolean shouldOverrideUserWhenGranting() {
return mOverrideUserWhenGranting;
}
/**
* @see #mShowNone
*/
public boolean shouldShowNone() {
return mShowNone;
}
public boolean isVisible() {
return mVisible;
}
@NonNull
public List<RequiredComponent> getRequiredComponents() {
return mRequiredComponents;
}
@NonNull
public List<Permission> getPermissions() {
return mPermissions;
}
@NonNull
public List<Permission> getAppOpPermissions() {
return mAppOpPermissions;
}
@NonNull
public List<AppOp> getAppOps() {
return mAppOps;
}
@NonNull
public List<PreferredActivity> getPreferredActivities() {
return mPreferredActivities;
}
@Nullable
public String getUiBehaviorName() {
return mUiBehaviorName;
}
/**
* Callback when this role is added to the system for the first time.
*
* @param context the {@code Context} to retrieve system services
*/
public void onRoleAdded(@NonNull Context context) {
if (mBehavior != null) {
mBehavior.onRoleAdded(this, context);
}
}
/**
* Check whether this role is available.
*
* @param user the user to check for
* @param context the {@code Context} to retrieve system services
*
* @return whether this role is available.
*/
public boolean isAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) {
if (!isAvailableBySdkVersion()) {
return false;
}
if (mBehavior != null) {
return mBehavior.isAvailableAsUser(this, user, context);
}
return true;
}
/**
* Check whether this role is available based on SDK version.
*
* @return whether this role is available based on SDK version
*/
boolean isAvailableBySdkVersion() {
// Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
if (mMinSdkVersion >= 34) {
return SdkLevel.isAtLeastU();
} else {
return Build.VERSION.SDK_INT >= mMinSdkVersion
&& Build.VERSION.SDK_INT <= mMaxSdkVersion;
}
}
/**
* Check whether this role is available, for current user.
*
* @param context the {@code Context} to retrieve system services
*
* @return whether this role is available.
*/
public boolean isAvailable(@NonNull Context context) {
return isAvailableAsUser(Process.myUserHandle(), context);
}
public boolean isStatic() {
return mStatic;
}
/**
* Get the default holders of this role, which will be added when the role is added for the
* first time.
*
* @param context the {@code Context} to retrieve system services
*
* @return the list of package names of the default holders
*/
@NonNull
public List<String> getDefaultHolders(@NonNull Context context) {
if (mDefaultHoldersResourceName == null) {
if (mBehavior != null) {
return mBehavior.getDefaultHolders(this, context);
}
return Collections.emptyList();
}
Resources resources = context.getResources();
int resourceId = resources.getIdentifier(mDefaultHoldersResourceName, "string", "android");
if (resourceId == 0) {
Log.w(LOG_TAG, "Cannot find resource for default holder: "
+ mDefaultHoldersResourceName);
return Collections.emptyList();
}
String defaultHolders;
try {
defaultHolders = resources.getString(resourceId);
} catch (Resources.NotFoundException e) {
Log.w(LOG_TAG, "Cannot get resource for default holder: " + mDefaultHoldersResourceName,
e);
return Collections.emptyList();
}
if (TextUtils.isEmpty(defaultHolders)) {
return Collections.emptyList();
}
if (isExclusive()) {
String packageName = getQualifiedDefaultHolderPackageName(defaultHolders, context);
if (packageName == null) {
return Collections.emptyList();
}
return Collections.singletonList(packageName);
} else {
List<String> packageNames = new ArrayList<>();
for (String defaultHolder : defaultHolders.split(DEFAULT_HOLDER_SEPARATOR)) {
String packageName = getQualifiedDefaultHolderPackageName(defaultHolder, context);
if (packageName != null) {
packageNames.add(packageName);
}
}
return packageNames;
}
}
@Nullable
private String getQualifiedDefaultHolderPackageName(@NonNull String defaultHolder,
@NonNull Context context) {
String packageName;
byte[] certificate;
int certificateSeparatorIndex = defaultHolder.indexOf(CERTIFICATE_SEPARATOR);
if (certificateSeparatorIndex != -1) {
packageName = defaultHolder.substring(0, certificateSeparatorIndex);
String certificateString = defaultHolder.substring(certificateSeparatorIndex + 1);
try {
certificate = new Signature(certificateString).toByteArray();
} catch (IllegalArgumentException e) {
Log.w(LOG_TAG, "Cannot parse signing certificate: " + defaultHolder, e);
return null;
}
} else {
packageName = defaultHolder;
certificate = null;
}
if (certificate != null) {
PackageManager packageManager = context.getPackageManager();
if (!packageManager.hasSigningCertificate(packageName, certificate,
PackageManager.CERT_INPUT_SHA256)) {
Log.w(LOG_TAG, "Default holder doesn't have required signing certificate: "
+ defaultHolder);
return null;
}
} else {
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for default holder: " + packageName);
return null;
}
if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
Log.w(LOG_TAG, "Default holder didn't specify a signing certificate and isn't a"
+ " system app: " + packageName);
return null;
}
}
return packageName;
}
/**
* Get the fallback holder of this role, which will be added whenever there are no role holders.
* <p>
* Should return {@code null} if this role {@link #mShowNone shows a "None" item}.
*
* @param context the {@code Context} to retrieve system services
*
* @return the package name of the fallback holder, or {@code null} if none
*/
@Nullable
public String getFallbackHolder(@NonNull Context context) {
if (!RoleManagerCompat.isRoleFallbackEnabledAsUser(this, Process.myUserHandle(), context)) {
return null;
}
if (mFallBackToDefaultHolder) {
return CollectionUtils.firstOrNull(getDefaultHolders(context));
}
if (mBehavior != null) {
return mBehavior.getFallbackHolder(this, context);
}
return null;
}
/**
* Check whether this role is allowed to bypass qualification, if enabled globally.
*
* @param context the {@code Context} to retrieve system services
*
* @return whether this role is allowed to bypass qualification
*/
public boolean shouldAllowBypassingQualification(@NonNull Context context) {
if (mBehavior != null) {
Boolean allowBypassingQualification = mBehavior.shouldAllowBypassingQualification(this,
context);
if (allowBypassingQualification != null) {
return allowBypassingQualification;
}
}
return mAllowBypassingQualification;
}
/**
* Check whether a package is qualified for this role, i.e. whether it contains all the required
* components (plus meeting some other general restrictions).
*
* @param packageName the package name to check for
* @param context the {@code Context} to retrieve system services
*
* @return whether the package is qualified for a role
*/
public boolean isPackageQualified(@NonNull String packageName, @NonNull Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
if (shouldAllowBypassingQualification(context)
&& RoleManagerCompat.isBypassingRoleQualification(roleManager)) {
return true;
}
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName);
return false;
}
if (!isPackageMinimallyQualifiedAsUser(applicationInfo, Process.myUserHandle(), context)) {
return false;
}
if (mBehavior != null) {
Boolean isPackageQualified = mBehavior.isPackageQualified(this, packageName, context);
if (isPackageQualified != null) {
return isPackageQualified;
}
}
int requiredComponentsSize = mRequiredComponents.size();
for (int i = 0; i < requiredComponentsSize; i++) {
RequiredComponent requiredComponent = mRequiredComponents.get(i);
if (!requiredComponent.isRequired(applicationInfo)) {
continue;
}
if (requiredComponent.getQualifyingComponentForPackage(packageName, context) == null) {
Log.i(LOG_TAG, packageName + " not qualified for " + mName
+ " due to missing " + requiredComponent);
return false;
}
}
if (mStatic && !getDefaultHolders(context).contains(packageName)) {
return false;
}
return true;
}
/**
* Get the list of packages that are qualified for this role, i.e. packages containing all the
* required components (plus meeting some other general restrictions).
*
* @param user the user to get the qualifying packages.
* @param context the {@code Context} to retrieve system services
*
* @return the list of packages that are qualified for this role
*/
@NonNull
public List<String> getQualifyingPackagesAsUser(@NonNull UserHandle user,
@NonNull Context context) {
List<String> qualifyingPackages = null;
if (mBehavior != null) {
qualifyingPackages = mBehavior.getQualifyingPackagesAsUser(this, user, context);
}
ArrayMap<String, ApplicationInfo> packageApplicationInfoMap = new ArrayMap<>();
if (qualifyingPackages == null) {
ArrayMap<String, ArraySet<RequiredComponent>> packageRequiredComponentsMap =
new ArrayMap<>();
int requiredComponentsSize = mRequiredComponents.size();
for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize;
requiredComponentsIndex++) {
RequiredComponent requiredComponent = mRequiredComponents.get(
requiredComponentsIndex);
if (!requiredComponent.isAvailable()) {
continue;
}
// This returns at most one component per package.
List<ComponentName> qualifyingComponents =
requiredComponent.getQualifyingComponentsAsUser(user, context);
int qualifyingComponentsSize = qualifyingComponents.size();
for (int qualifyingComponentsIndex = 0;
qualifyingComponentsIndex < qualifyingComponentsSize;
++qualifyingComponentsIndex) {
ComponentName componentName = qualifyingComponents.get(
qualifyingComponentsIndex);
String packageName = componentName.getPackageName();
ArraySet<RequiredComponent> packageRequiredComponents =
packageRequiredComponentsMap.get(packageName);
if (packageRequiredComponents == null) {
packageRequiredComponents = new ArraySet<>();
packageRequiredComponentsMap.put(packageName, packageRequiredComponents);
}
packageRequiredComponents.add(requiredComponent);
}
}
qualifyingPackages = new ArrayList<>();
int packageRequiredComponentsMapSize = packageRequiredComponentsMap.size();
for (int packageRequiredComponentsMapIndex = 0;
packageRequiredComponentsMapIndex < packageRequiredComponentsMapSize;
packageRequiredComponentsMapIndex++) {
String packageName = packageRequiredComponentsMap.keyAt(
packageRequiredComponentsMapIndex);
ArraySet<RequiredComponent> packageRequiredComponents =
packageRequiredComponentsMap.valueAt(packageRequiredComponentsMapIndex);
ApplicationInfo applicationInfo = packageApplicationInfoMap.get(packageName);
if (applicationInfo == null) {
applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user,
context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName
+ ", user: " + user.getIdentifier());
continue;
}
packageApplicationInfoMap.put(packageName, applicationInfo);
}
boolean hasAllRequiredComponents = true;
for (int requiredComponentsIndex = 0;
requiredComponentsIndex < requiredComponentsSize;
requiredComponentsIndex++) {
RequiredComponent requiredComponent = mRequiredComponents.get(
requiredComponentsIndex);
if (!requiredComponent.isRequired(applicationInfo)) {
continue;
}
if (!packageRequiredComponents.contains(requiredComponent)) {
hasAllRequiredComponents = false;
break;
}
}
if (hasAllRequiredComponents) {
qualifyingPackages.add(packageName);
}
}
}
int qualifyingPackagesSize = qualifyingPackages.size();
for (int i = 0; i < qualifyingPackagesSize; ) {
String packageName = qualifyingPackages.get(i);
ApplicationInfo applicationInfo = packageApplicationInfoMap.get(packageName);
if (applicationInfo == null) {
applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user,
context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName
+ ", user: " + user.getIdentifier());
continue;
}
packageApplicationInfoMap.put(packageName, applicationInfo);
}
if (!isPackageMinimallyQualifiedAsUser(applicationInfo, user, context)) {
qualifyingPackages.remove(i);
qualifyingPackagesSize--;
} else {
i++;
}
}
return qualifyingPackages;
}
private boolean isPackageMinimallyQualifiedAsUser(@NonNull ApplicationInfo applicationInfo,
@NonNull UserHandle user,
@NonNull Context context) {
String packageName = applicationInfo.packageName;
if (Objects.equals(packageName, PACKAGE_NAME_ANDROID_SYSTEM)) {
return false;
}
if (mSystemOnly && (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
return false;
}
if (!applicationInfo.enabled) {
return false;
}
if (applicationInfo.isInstantApp()) {
return false;
}
PackageManager userPackageManager = UserUtils.getUserContext(context, user)
.getPackageManager();
List<SharedLibraryInfo> declaredLibraries = userPackageManager.getDeclaredSharedLibraries(
packageName, 0);
final int libCount = declaredLibraries.size();
for (int i = 0; i < libCount; i++) {
SharedLibraryInfo sharedLibrary = declaredLibraries.get(i);
if (sharedLibrary.getType() != SharedLibraryInfo.TYPE_DYNAMIC) {
return false;
}
}
return true;
}
/**
* Grant this role to an application.
*
* @param packageName the package name of the application to be granted this role to
* @param dontKillApp whether this application should not be killed despite changes
* @param overrideUser whether to override user when granting privileges
* @param context the {@code Context} to retrieve system services
*/
public void grant(@NonNull String packageName, boolean dontKillApp,
boolean overrideUser, @NonNull Context context) {
boolean permissionOrAppOpChanged = Permissions.grant(packageName,
Permissions.filterBySdkVersion(mPermissions),
SdkLevel.isAtLeastS() ? !mSystemOnly : true, overrideUser, true, false, false,
context);
List<String> appOpPermissionsToGrant = Permissions.filterBySdkVersion(mAppOpPermissions);
int appOpPermissionsSize = appOpPermissionsToGrant.size();
for (int i = 0; i < appOpPermissionsSize; i++) {
String appOpPermission = appOpPermissionsToGrant.get(i);
AppOpPermissions.grant(packageName, appOpPermission, overrideUser, context);
}
int appOpsSize = mAppOps.size();
for (int i = 0; i < appOpsSize; i++) {
AppOp appOp = mAppOps.get(i);
appOp.grant(packageName, context);
}
int preferredActivitiesSize = mPreferredActivities.size();
for (int i = 0; i < preferredActivitiesSize; i++) {
PreferredActivity preferredActivity = mPreferredActivities.get(i);
preferredActivity.configure(packageName, context);
}
if (mBehavior != null) {
mBehavior.grant(this, packageName, context);
}
if (!dontKillApp && permissionOrAppOpChanged && !Permissions.isRuntimePermissionsSupported(
packageName, context)) {
killApp(packageName, context);
}
}
/**
* Revoke this role from an application.
*
* @param packageName the package name of the application to be granted this role to
* @param dontKillApp whether this application should not be killed despite changes
* @param overrideSystemFixedPermissions whether system-fixed permissions can be revoked
* @param context the {@code Context} to retrieve system services
*/
public void revoke(@NonNull String packageName, boolean dontKillApp,
boolean overrideSystemFixedPermissions, @NonNull Context context) {
RoleManager roleManager = context.getSystemService(RoleManager.class);
List<String> otherRoleNames = roleManager.getHeldRolesFromController(packageName);
otherRoleNames.remove(mName);
List<String> permissionsToRevoke = Permissions.filterBySdkVersion(mPermissions);
ArrayMap<String, Role> roles = Roles.get(context);
int otherRoleNamesSize = otherRoleNames.size();
for (int i = 0; i < otherRoleNamesSize; i++) {
String roleName = otherRoleNames.get(i);
Role role = roles.get(roleName);
permissionsToRevoke.removeAll(Permissions.filterBySdkVersion(role.mPermissions));
}
boolean permissionOrAppOpChanged = Permissions.revoke(packageName, permissionsToRevoke,
true, false, overrideSystemFixedPermissions, context);
List<String> appOpPermissionsToRevoke = Permissions.filterBySdkVersion(mAppOpPermissions);
for (int i = 0; i < otherRoleNamesSize; i++) {
String roleName = otherRoleNames.get(i);
Role role = roles.get(roleName);
appOpPermissionsToRevoke.removeAll(
Permissions.filterBySdkVersion(role.mAppOpPermissions));
}
int appOpPermissionsSize = appOpPermissionsToRevoke.size();
for (int i = 0; i < appOpPermissionsSize; i++) {
String appOpPermission = appOpPermissionsToRevoke.get(i);
AppOpPermissions.revoke(packageName, appOpPermission, context);
}
List<AppOp> appOpsToRevoke = new ArrayList<>(mAppOps);
for (int i = 0; i < otherRoleNamesSize; i++) {
String roleName = otherRoleNames.get(i);
Role role = roles.get(roleName);
appOpsToRevoke.removeAll(role.mAppOps);
}
int appOpsSize = appOpsToRevoke.size();
for (int i = 0; i < appOpsSize; i++) {
AppOp appOp = appOpsToRevoke.get(i);
appOp.revoke(packageName, context);
}
// TODO: Revoke preferred activities? But this is unnecessary for most roles using it as
// they have fallback holders. Moreover, clearing the preferred activity might result in
// other system components listening to preferred activity change get notified for the
// wrong thing when we are removing a exclusive role holder for adding another.
if (mBehavior != null) {
mBehavior.revoke(this, packageName, context);
}
if (!dontKillApp && permissionOrAppOpChanged) {
killApp(packageName, context);
}
}
private void killApp(@NonNull String packageName, @NonNull Context context) {
if (DEBUG) {
Log.i(LOG_TAG, "Killing " + packageName + " due to "
+ Thread.currentThread().getStackTrace()[3].getMethodName()
+ "(" + mName + ")");
}
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName);
return;
}
ActivityManager activityManager = context.getSystemService(ActivityManager.class);
activityManager.killUid(applicationInfo.uid, "Permission or app op changed");
}
/**
* Callback when a role holder (other than "none") was added.
*
* @param packageName the package name of the role holder
* @param user the user for the role
* @param context the {@code Context} to retrieve system services
*/
public void onHolderAddedAsUser(@NonNull String packageName, @NonNull UserHandle user,
@NonNull Context context) {
RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, user, context);
}
/**
* Callback when a role holder (other than "none") was selected in the UI and added
* successfully.
*
* @param packageName the package name of the role holder
* @param user the user for the role
* @param context the {@code Context} to retrieve system services
*/
public void onHolderSelectedAsUser(@NonNull String packageName, @NonNull UserHandle user,
@NonNull Context context) {
if (mBehavior != null) {
mBehavior.onHolderSelectedAsUser(this, packageName, user, context);
}
}
/**
* Callback when a role holder changed.
*
* @param user the user for the role
* @param context the {@code Context} to retrieve system services
*/
public void onHolderChangedAsUser(@NonNull UserHandle user,
@NonNull Context context) {
if (mBehavior != null) {
mBehavior.onHolderChangedAsUser(this, user, context);
}
}
/**
* Callback when the "none" role holder was selected in the UI.
*
* @param user the user for the role
* @param context the {@code Context} to retrieve system services
*/
public void onNoneHolderSelectedAsUser(@NonNull UserHandle user, @NonNull Context context) {
RoleManagerCompat.setRoleFallbackEnabledAsUser(this, false, user, context);
}
@Override
public String toString() {
return "Role{"
+ "mName='" + mName + '\''
+ ", mAllowBypassingQualification=" + mAllowBypassingQualification
+ ", mBehavior=" + mBehavior
+ ", mDefaultHoldersResourceName=" + mDefaultHoldersResourceName
+ ", mDescriptionResource=" + mDescriptionResource
+ ", mExclusive=" + mExclusive
+ ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder
+ ", mLabelResource=" + mLabelResource
+ ", mMaxSdkVersion=" + mMaxSdkVersion
+ ", mMinSdkVersion=" + mMinSdkVersion
+ ", mOverrideUserWhenGranting=" + mOverrideUserWhenGranting
+ ", mRequestDescriptionResource=" + mRequestDescriptionResource
+ ", mRequestTitleResource=" + mRequestTitleResource
+ ", mRequestable=" + mRequestable
+ ", mSearchKeywordsResource=" + mSearchKeywordsResource
+ ", mShortLabelResource=" + mShortLabelResource
+ ", mShowNone=" + mShowNone
+ ", mStatic=" + mStatic
+ ", mSystemOnly=" + mSystemOnly
+ ", mVisible=" + mVisible
+ ", mRequiredComponents=" + mRequiredComponents
+ ", mPermissions=" + mPermissions
+ ", mAppOpPermissions=" + mAppOpPermissions
+ ", mAppOps=" + mAppOps
+ ", mPreferredActivities=" + mPreferredActivities
+ ", mUiBehaviorName=" + mUiBehaviorName
+ '}';
}
}