diff options
11 files changed, 276 insertions, 31 deletions
diff --git a/proguard.flags b/proguard.flags index 46a929e3..05defe97 100644 --- a/proguard.flags +++ b/proguard.flags @@ -6,3 +6,8 @@ *; } -dontwarn androidx.core.** + +# Keep classes that implements RoleAvailabilityProvider, which are used by reflection. +-keep class * implements com.android.packageinstaller.role.model.RoleAvailabilityProvider { + *; +} diff --git a/res/xml/roles.xml b/res/xml/roles.xml index 454f405b..1fca6dcb 100644 --- a/res/xml/roles.xml +++ b/res/xml/roles.xml @@ -147,7 +147,11 @@ </role> <!--- @see android.telecom.DefaultDialerManager --> - <role name="android.app.role.DIALER" exclusive="true" label="@string/role_label_dialer"> + <role + name="android.app.role.DIALER" + availabilityProvider="DialerRoleAvailabilityProvider" + exclusive="true" + label="@string/role_label_dialer"> <required-components> <activity> <intent-filter> @@ -196,7 +200,11 @@ </role> <!--- @see com.android.internal.telephony.SmsApplication --> - <role name="android.app.role.SMS" exclusive="true" label="@string/role_label_sms"> + <role + name="android.app.role.SMS" + availabilityProvider="SmsRoleAvailabilityProvider" + exclusive="true" + label="@string/role_label_sms"> <required-components> <receiver permission="android.permission.BROADCAST_SMS"> <intent-filter> diff --git a/src/com/android/packageinstaller/role/model/DialerRoleAvailabilityProvider.java b/src/com/android/packageinstaller/role/model/DialerRoleAvailabilityProvider.java new file mode 100644 index 00000000..9638bc8f --- /dev/null +++ b/src/com/android/packageinstaller/role/model/DialerRoleAvailabilityProvider.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 com.android.packageinstaller.role.model; + +import android.content.Context; +import android.os.UserHandle; +import android.telephony.TelephonyManager; + +import androidx.annotation.NonNull; + +/** + * Class for determining whether the dialer role is available. + * + * @see com.android.settings.applications.DefaultAppSettings + * @see com.android.settings.applications.defaultapps.DefaultPhonePreferenceController#isAvailable() + */ +public class DialerRoleAvailabilityProvider implements RoleAvailabilityProvider { + + @Override + public boolean isRoleAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + return telephonyManager.isVoiceCapable(); + } +} diff --git a/src/com/android/packageinstaller/role/model/Role.java b/src/com/android/packageinstaller/role/model/Role.java index 2ea8d462..cfe627ea 100644 --- a/src/com/android/packageinstaller/role/model/Role.java +++ b/src/com/android/packageinstaller/role/model/Role.java @@ -21,11 +21,13 @@ import android.app.role.RoleManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.StringRes; import com.android.packageinstaller.role.utils.PackageUtils; @@ -63,10 +65,10 @@ public class Role { private final String mName; /** - * The string resource for the label of this role. + * Whether this role is available in managed profile, i.e. work profile. */ - @StringRes - private final int mLabelResource; + @Nullable + private final RoleAvailabilityProvider mAvailabilityProvider; /** * Whether this role is exclusive, i.e. allows at most one holder. @@ -74,6 +76,12 @@ public class Role { private final boolean mExclusive; /** + * The string resource for the label of this role. + */ + @StringRes + private final int mLabelResource; + + /** * The required components for an application to qualify for this role. */ @NonNull @@ -97,12 +105,14 @@ public class Role { @NonNull private final List<PreferredActivity> mPreferredActivities; - public Role(@NonNull String name, @StringRes int labelResource, boolean exclusive, + public Role(@NonNull String name, @Nullable RoleAvailabilityProvider availabilityProvider, + boolean exclusive, @StringRes int labelResource, @NonNull List<RequiredComponent> requiredComponents, @NonNull List<String> permissions, @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities) { mName = name; - mLabelResource = labelResource; + mAvailabilityProvider = availabilityProvider; mExclusive = exclusive; + mLabelResource = labelResource; mRequiredComponents = requiredComponents; mPermissions = permissions; mAppOps = appOps; @@ -114,15 +124,20 @@ public class Role { return mName; } - @StringRes - public int getLabelResource() { - return mLabelResource; + @Nullable + public RoleAvailabilityProvider getAvailabilityProvider() { + return mAvailabilityProvider; } public boolean isExclusive() { return mExclusive; } + @StringRes + public int getLabelResource() { + return mLabelResource; + } + @NonNull public List<RequiredComponent> getRequiredComponents() { return mRequiredComponents; @@ -144,6 +159,32 @@ public class Role { } /** + * 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 (mAvailabilityProvider != null) { + return mAvailabilityProvider.isRoleAvailableAsUser(user, context); + } + return true; + } + + /** + * 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); + } + + /** * Check whether a package is qualified for this role, i.e. whether it contains all the required * components. * @@ -270,30 +311,31 @@ public class Role { */ public void revoke(@NonNull String packageName, boolean mayKillApp, boolean overrideSystemFixedPermissions, @NonNull Context context) { - List<String> remainingRolesHeld = context.getSystemService(RoleManager.class) - .getHeldRolesFromController(packageName); - remainingRolesHeld.remove(mName); + RoleManager roleManager = context.getSystemService(RoleManager.class); + List<String> otherRoleNames = roleManager.getHeldRolesFromController(packageName); + otherRoleNames.remove(mName); - // Revoke permissions - ArrayMap<String, Role> roles = Roles.getRoles(context); List<String> permissionsToRevoke = new ArrayList<>(mPermissions); - int size = remainingRolesHeld.size(); - for (int i = 0; i < size; i++) { - String remainingRole = remainingRolesHeld.get(i); - permissionsToRevoke.removeAll(roles.get(remainingRole).getPermissions()); + ArrayMap<String, Role> roles = Roles.getRoles(context); + int otherRoleNamesSize = otherRoleNames.size(); + for (int i = 0; i < otherRoleNamesSize; i++) { + String roleName = otherRoleNames.get(i); + Role role = roles.get(roleName); + permissionsToRevoke.removeAll(role.getPermissions()); } boolean permissionOrAppOpChanged = Permissions.revoke(packageName, permissionsToRevoke, overrideSystemFixedPermissions, context); - // Revoke appops List<AppOp> appOpsToRevoke = new ArrayList<>(mAppOps); - size = remainingRolesHeld.size(); - for (int i = 0; i < size; i++) { - appOpsToRevoke.removeAll(roles.get(remainingRolesHeld.get(i)).getAppOps()); + for (int i = 0; i < otherRoleNamesSize; i++) { + String roleName = otherRoleNames.get(i); + Role role = roles.get(roleName); + appOpsToRevoke.removeAll(role.getAppOps()); } int appOpsSize = appOpsToRevoke.size(); for (int i = 0; i < appOpsSize; i++) { - permissionOrAppOpChanged |= appOpsToRevoke.get(i).revoke(packageName, context); + AppOp appOp = appOpsToRevoke.get(i); + permissionOrAppOpChanged |= appOp.revoke(packageName, context); } // TODO: STOPSHIP: Revoke preferred activities? @@ -303,7 +345,7 @@ public class Role { } } - private void killApp(@NonNull String packageName, @NonNull Context context) { + private static void killApp(@NonNull String packageName, @NonNull Context context) { ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context); if (applicationInfo == null) { Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName); @@ -317,7 +359,9 @@ public class Role { public String toString() { return "Role{" + "mName='" + mName + '\'' + + ", mAvailabilityProvider=" + mAvailabilityProvider + ", mExclusive=" + mExclusive + + ", mLabelResource=" + mLabelResource + ", mRequiredComponents=" + mRequiredComponents + ", mPermissions=" + mPermissions + ", mAppOps=" + mAppOps @@ -335,7 +379,9 @@ public class Role { } Role role = (Role) object; return mExclusive == role.mExclusive + && mLabelResource == role.mLabelResource && Objects.equals(mName, role.mName) + && Objects.equals(mAvailabilityProvider, role.mAvailabilityProvider) && Objects.equals(mRequiredComponents, role.mRequiredComponents) && Objects.equals(mPermissions, role.mPermissions) && Objects.equals(mAppOps, role.mAppOps) @@ -344,7 +390,7 @@ public class Role { @Override public int hashCode() { - return Objects.hash(mName, mExclusive, mRequiredComponents, mPermissions, mAppOps, - mPreferredActivities); + return Objects.hash(mName, mAvailabilityProvider, mExclusive, mLabelResource, + mRequiredComponents, mPermissions, mAppOps, mPreferredActivities); } } diff --git a/src/com/android/packageinstaller/role/model/RoleAvailabilityProvider.java b/src/com/android/packageinstaller/role/model/RoleAvailabilityProvider.java new file mode 100644 index 00000000..4c7207d5 --- /dev/null +++ b/src/com/android/packageinstaller/role/model/RoleAvailabilityProvider.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 com.android.packageinstaller.role.model; + +import android.content.Context; +import android.os.UserHandle; + +import androidx.annotation.NonNull; + +/** + * Interface for determining whether a role is available. + */ +public interface RoleAvailabilityProvider { + + /** + * Check whether a role is available + * + * @param user the user to check for + * @param context the {@code Context} to retrieve system services + * + * @return Whether the role is available + */ + boolean isRoleAvailableAsUser(@NonNull UserHandle user, @NonNull Context context); +} diff --git a/src/com/android/packageinstaller/role/model/Roles.java b/src/com/android/packageinstaller/role/model/Roles.java index 8d8afce6..423bd5c2 100644 --- a/src/com/android/packageinstaller/role/model/Roles.java +++ b/src/com/android/packageinstaller/role/model/Roles.java @@ -70,9 +70,10 @@ public class Roles { private static final String TAG_PREFERRED_ACTIVITIES = "preferred-activities"; private static final String TAG_PREFERRED_ACTIVITY = "preferred-activity"; private static final String ATTRIBUTE_NAME = "name"; - private static final String ATTRIBUTE_PERMISSION = "permission"; + private static final String ATTRIBUTE_AVAILABILITY_PROVIDER = "availabilityProvider"; private static final String ATTRIBUTE_EXCLUSIVE = "exclusive"; private static final String ATTRIBUTE_LABEL = "label"; + private static final String ATTRIBUTE_PERMISSION = "permission"; private static final String ATTRIBUTE_SCHEME = "scheme"; private static final String ATTRIBUTE_MIME_TYPE = "mimeType"; private static final String ATTRIBUTE_VALUE = "value"; @@ -290,6 +291,25 @@ public class Roles { return null; } + String availabilityProviderClassSimpleName = getAttributeValue(parser, + ATTRIBUTE_AVAILABILITY_PROVIDER); + RoleAvailabilityProvider availabilityProvider; + if (availabilityProviderClassSimpleName != null) { + String availabilityProviderClassName = Roles.class.getPackage().getName() + '.' + + availabilityProviderClassSimpleName; + try { + availabilityProvider = (RoleAvailabilityProvider) Class.forName( + availabilityProviderClassName).newInstance(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + throwOrLogMessage("Unable to instantiate availability provider: " + + availabilityProviderClassName, e); + skipCurrentTag(parser); + return null; + } + } else { + availabilityProvider = null; + } + Boolean exclusive = requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true, TAG_ROLE); if (exclusive == null) { @@ -363,8 +383,8 @@ public class Roles { if (preferredActivities == null) { preferredActivities = Collections.emptyList(); } - return new Role(name, labelResource, exclusive, requiredComponents, permissions, appOps, - preferredActivities); + return new Role(name, availabilityProvider, exclusive, labelResource, requiredComponents, + permissions, appOps, preferredActivities); } @NonNull diff --git a/src/com/android/packageinstaller/role/model/SmsRoleAvailabilityProvider.java b/src/com/android/packageinstaller/role/model/SmsRoleAvailabilityProvider.java new file mode 100644 index 00000000..d4d82427 --- /dev/null +++ b/src/com/android/packageinstaller/role/model/SmsRoleAvailabilityProvider.java @@ -0,0 +1,50 @@ +/* + * 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.packageinstaller.role.model; + +import android.content.Context; +import android.os.UserHandle; +import android.os.UserManager; +import android.telephony.TelephonyManager; + +import androidx.annotation.NonNull; + +/** + * Class for determining whether the SMS role is available. + * + * @see com.android.settings.applications.DefaultAppSettings + * @see com.android.settings.applications.defaultapps.DefaultSmsPreferenceController#isAvailable() + */ +public class SmsRoleAvailabilityProvider implements RoleAvailabilityProvider { + + @Override + public boolean isRoleAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) { + UserManager userManager = context.getSystemService(UserManager.class); + if (userManager.isManagedProfile(user.getIdentifier())) { + return false; + } + // FIXME: STOPSHIP: Add an appropriate @SystemApi for this. + //if (userManager.getUserInfo(user.getIdentifier()).isRestricted()) { + // return false; + //} + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + if (!telephonyManager.isSmsCapable()) { + return false; + } + return true; + } +} diff --git a/src/com/android/packageinstaller/role/service/RoleControllerServiceImpl.java b/src/com/android/packageinstaller/role/service/RoleControllerServiceImpl.java index f96b24ae..37049b60 100644 --- a/src/com/android/packageinstaller/role/service/RoleControllerServiceImpl.java +++ b/src/com/android/packageinstaller/role/service/RoleControllerServiceImpl.java @@ -128,10 +128,20 @@ public class RoleControllerServiceImpl extends RoleControllerService { Log.e(LOG_TAG, "callback cannot be null"); return; } + ArrayMap<String, Role> roles = Roles.getRoles(this); + List<String> roleNames = new ArrayList<>(); + int rolesSize = roles.size(); + for (int i = 0; i < rolesSize; i++) { + Role role = roles.valueAt(i); + if (!role.isAvailable(this)) { + continue; + } + roleNames.add(role.getName()); + } // TODO: Clean up holders of roles that will be removed. - List<String> roleNames = new ArrayList<>(roles.keySet()); mRoleManager.setRoleNamesFromController(roleNames); + //TODO grant default permissions and appops Log.i(LOG_TAG, "Granting defaults for user " + UserHandle.myUserId()); callback.onSuccess(); @@ -146,6 +156,11 @@ public class RoleControllerServiceImpl extends RoleControllerService { callback.onFailure(); return; } + if (!role.isAvailable(this)) { + Log.e(LOG_TAG, "Role is unavailable: " + roleName); + callback.onFailure(); + return; + } ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, this); if (applicationInfo == null) { @@ -207,6 +222,11 @@ public class RoleControllerServiceImpl extends RoleControllerService { callback.onFailure(); return; } + if (!role.isAvailable(this)) { + Log.e(LOG_TAG, "Role is unavailable: " + roleName); + callback.onFailure(); + return; + } boolean removed = removeRoleHolderInternal(role, packageName); if (!removed) { @@ -227,6 +247,11 @@ public class RoleControllerServiceImpl extends RoleControllerService { callback.onFailure(); return; } + if (!role.isAvailable(this)) { + Log.e(LOG_TAG, "Role is unavailable: " + roleName); + callback.onFailure(); + return; + } List<String> packageNames = mRoleManager.getRoleHolders(roleName); int packageNamesSize = packageNames.size(); diff --git a/src/com/android/packageinstaller/role/ui/DefaultAppActivity.java b/src/com/android/packageinstaller/role/ui/DefaultAppActivity.java index 67b2dc9b..1b5e94fa 100644 --- a/src/com/android/packageinstaller/role/ui/DefaultAppActivity.java +++ b/src/com/android/packageinstaller/role/ui/DefaultAppActivity.java @@ -67,6 +67,11 @@ public class DefaultAppActivity extends FragmentActivity { finish(); return; } + if (!role.isAvailableAsUser(user, this)) { + Log.e(LOG_TAG, "Role is unavailable: " + roleName); + finish(); + return; + } if (savedInstanceState == null) { DefaultAppFragment fragment = DefaultAppFragment.newInstance(roleName, user); diff --git a/src/com/android/packageinstaller/role/ui/RequestRoleActivity.java b/src/com/android/packageinstaller/role/ui/RequestRoleActivity.java index 591d220f..0c541730 100644 --- a/src/com/android/packageinstaller/role/ui/RequestRoleActivity.java +++ b/src/com/android/packageinstaller/role/ui/RequestRoleActivity.java @@ -77,6 +77,12 @@ public class RequestRoleActivity extends FragmentActivity { return; } + if (!role.isAvailable(this)) { + Log.e(LOG_TAG, "Role is unavailable: " + mRoleName); + finish(); + return; + } + if (PackageUtils.getApplicationInfo(mPackageName, this) == null) { Log.w(LOG_TAG, "Unknown application: " + mPackageName); finish(); diff --git a/src/com/android/packageinstaller/role/ui/RoleListLiveData.java b/src/com/android/packageinstaller/role/ui/RoleListLiveData.java index c4435a77..3a5b617b 100644 --- a/src/com/android/packageinstaller/role/ui/RoleListLiveData.java +++ b/src/com/android/packageinstaller/role/ui/RoleListLiveData.java @@ -91,6 +91,10 @@ public class RoleListLiveData extends AsyncTaskLiveData<List<RoleItem>> continue; } + if (!role.isAvailableAsUser(mUser, mContext)) { + continue; + } + if (role.getQualifyingPackagesAsUser(mUser, mContext).isEmpty()) { continue; } |