diff options
Diffstat (limited to 'service')
19 files changed, 1037 insertions, 202 deletions
diff --git a/service/Android.bp b/service/Android.bp index 6f851c4d2..b6e85b0fc 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -101,6 +101,7 @@ java_sdk_library { "modules-utils-backgroundthread", "modules-utils-build", "modules-utils-os", + // framework-permission-s already includes com.android.permission.flags-aconfig-java "role-controller", "safety-center-config", "safety-center-internal-data", diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt index 30fbab484..a3db89370 100644 --- a/service/api/system-server-current.txt +++ b/service/api/system-server-current.txt @@ -1,4 +1,18 @@ // Signature format: 2.0 +package com.android.ecm { + + @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public class EnhancedConfirmationCallTrackerService extends android.telecom.InCallService { + ctor public EnhancedConfirmationCallTrackerService(); + } + + @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public interface EnhancedConfirmationManagerLocal { + method public void addOngoingCall(@NonNull android.telecom.Call); + method public void clearOngoingCalls(); + method public void removeOngoingCall(@NonNull String); + } + +} + package com.android.permission.persistence { public interface RuntimePermissionsPersistence { diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index 4d4d6e050..bad5c7666 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -2,6 +2,10 @@ # RoleParser.applyJarjarTransform(), by adding NO_IFTTT=reason to your commit # message. # LINT.IfChange +rule android.app.admin.flags.*FeatureFlags* com.android.permission.jarjar.@0 +rule android.app.admin.flags.FeatureFlags* com.android.permission.jarjar.@0 +rule android.app.admin.flags.FeatureFlags com.android.permission.jarjar.@0 +rule android.app.admin.flags.Flags com.android.permission.jarjar.@0 rule android.app.appfunctions.flags.*FeatureFlags* com.android.permission.jarjar.@0 rule android.app.appfunctions.flags.FeatureFlags* com.android.permission.jarjar.@0 rule android.app.appfunctions.flags.FeatureFlags com.android.permission.jarjar.@0 @@ -10,6 +14,10 @@ rule android.companion.virtualdevice.flags.*FeatureFlags* com.android.permission rule android.companion.virtualdevice.flags.FeatureFlags* com.android.permission.jarjar.@0 rule android.companion.virtualdevice.flags.FeatureFlags com.android.permission.jarjar.@0 rule android.companion.virtualdevice.flags.Flags com.android.permission.jarjar.@0 +rule android.content.pm.*FeatureFlags* com.android.permission.jarjar.@0 +rule android.content.pm.FeatureFlags* com.android.permission.jarjar.@0 +rule android.content.pm.FeatureFlags com.android.permission.jarjar.@0 +rule android.content.pm.Flags com.android.permission.jarjar.@0 rule android.os.*FeatureFlags* com.android.permission.jarjar.@0 rule android.os.FeatureFlags* com.android.permission.jarjar.@0 rule android.os.FeatureFlags com.android.permission.jarjar.@0 @@ -34,6 +42,10 @@ rule com.android.safetycenter.annotations.** com.android.permission.jarjar.@0 rule com.android.safetycenter.internaldata.** com.android.permission.jarjar.@0 rule com.android.safetycenter.pendingintents.** com.android.permission.jarjar.@0 rule com.android.safetycenter.resources.** com.android.permission.jarjar.@0 +rule com.android.settingslib.flags.*FeatureFlags* com.android.permission.jarjar.@0 +rule com.android.settingslib.flags.FeatureFlags* com.android.permission.jarjar.@0 +rule com.android.settingslib.flags.FeatureFlags com.android.permission.jarjar.@0 +rule com.android.settingslib.flags.Flags com.android.permission.jarjar.@0 rule com.google.protobuf.** com.android.permission.jarjar.@0 rule kotlin.** com.android.permission.jarjar.@0 rule com.android.permissioncontroller.PermissionControllerStatsLog com.android.permission.jarjar.@0 diff --git a/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java new file mode 100644 index 000000000..9117d6558 --- /dev/null +++ b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 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.ecm; + +import android.annotation.FlaggedApi; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.annotation.TargetApi; +import android.content.Intent; +import android.os.Build; +import android.permission.flags.Flags; +import android.telecom.Call; +import android.telecom.InCallService; + +import com.android.server.LocalManagerRegistry; + +/** + * @hide + * + * This InCallService tracks called (both incoming and outgoing), and sends their information to the + * EnhancedConfirmationService + * + **/ +@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@TargetApi(Build.VERSION_CODES.BAKLAVA) +public class EnhancedConfirmationCallTrackerService extends InCallService { + private EnhancedConfirmationManagerLocal mEnhancedConfirmationManagerLocal; + + @Override + public void onCreate() { + super.onCreate(); + if (Flags.unknownCallPackageInstallBlockingEnabled()) { + mEnhancedConfirmationManagerLocal = + LocalManagerRegistry.getManager(EnhancedConfirmationManagerLocal.class); + } + } + + @Override + public void onCallAdded(@Nullable Call call) { + if (mEnhancedConfirmationManagerLocal == null || call == null) { + return; + } + + mEnhancedConfirmationManagerLocal.addOngoingCall(call); + } + + @Override + public void onCallRemoved(@Nullable Call call) { + if (mEnhancedConfirmationManagerLocal == null || call == null) { + return; + } + + mEnhancedConfirmationManagerLocal.removeOngoingCall(call.getDetails().getId()); + } + + /** + * When unbound, we should assume all calls have finished. Notify the system of such. + */ + public boolean onUnbind(@Nullable Intent intent) { + if (mEnhancedConfirmationManagerLocal != null) { + mEnhancedConfirmationManagerLocal.clearOngoingCalls(); + } + return super.onUnbind(intent); + } +} diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java new file mode 100644 index 000000000..483071716 --- /dev/null +++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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.ecm; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.TargetApi; +import android.os.Build; +import android.permission.flags.Flags; +import android.telecom.Call; + +/** + * @hide + * + * In-process API for the Enhanced Confirmation Service + */ +@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) +@TargetApi(Build.VERSION_CODES.BAKLAVA) +public interface EnhancedConfirmationManagerLocal { + /** + * Inform the enhanced confirmation service of an ongoing call + * + * @param call The call to potentially track + * + */ + void addOngoingCall(@NonNull Call call); + + /** + * Inform the enhanced confirmation service that a call has ended + * + * @param callId The ID of the call to stop tracking + * + */ + void removeOngoingCall(@NonNull String callId); + + /** + * Informs the enhanced confirmation service it should clear out any ongoing calls + */ + void clearOngoingCalls(); +} diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java new file mode 100644 index 000000000..b34eac9f4 --- /dev/null +++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.ecm; + +import android.annotation.NonNull; +import android.annotation.TargetApi; +import android.os.Build; +import android.permission.flags.Flags; +import android.telecom.Call; + +/** @hide */ +@TargetApi(Build.VERSION_CODES.BAKLAVA) +class EnhancedConfirmationManagerLocalImpl implements EnhancedConfirmationManagerLocal { + + private final EnhancedConfirmationService mService; + + EnhancedConfirmationManagerLocalImpl(EnhancedConfirmationService service) { + if (Flags.unknownCallPackageInstallBlockingEnabled()) { + mService = service; + } else { + mService = null; + } + } + + @Override + public void addOngoingCall(@NonNull Call call) { + if (mService != null) { + mService.addOngoingCall(call); + } + } + + @Override + public void removeOngoingCall(@NonNull String callId) { + if (mService != null) { + mService.removeOngoingCall(callId); + } + } + + @Override + public void clearOngoingCalls() { + if (mService != null) { + mService.clearOngoingCalls(); + } + } +} diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java index 708884e85..65fde6daf 100644 --- a/service/java/com/android/ecm/EnhancedConfirmationService.java +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -19,11 +19,15 @@ package com.android.ecm; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.ecm.EnhancedConfirmationManager; import android.app.ecm.IEnhancedConfirmationManager; import android.app.role.RoleManager; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; @@ -31,25 +35,33 @@ import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.SignedPackage; +import android.database.Cursor; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.SystemConfigManager; import android.os.UserHandle; import android.permission.flags.Flags; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.PhoneLookup; +import android.telecom.Call; +import android.telecom.PhoneAccount; +import android.telephony.TelephonyManager; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.android.internal.util.Preconditions; import com.android.permission.util.UserUtils; +import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -66,16 +78,35 @@ import java.util.Set; @Keep @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +@SuppressLint("MissingPermission") public class EnhancedConfirmationService extends SystemService { private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName(); private Map<String, List<byte[]>> mTrustedPackageCertDigests; private Map<String, List<byte[]>> mTrustedInstallerCertDigests; + // A map of call ID to call type + private final Map<String, Integer> mOngoingCalls = new ArrayMap<>(); + + private static final int CALL_TYPE_UNTRUSTED = 0; + private static final int CALL_TYPE_TRUSTED = 1; + private static final int CALL_TYPE_EMERGENCY = 2; + @IntDef(flag = true, value = { + CALL_TYPE_UNTRUSTED, + CALL_TYPE_TRUSTED, + CALL_TYPE_EMERGENCY + }) + @Retention(RetentionPolicy.SOURCE) + @interface CallType {} public EnhancedConfirmationService(@NonNull Context context) { super(context); + LocalManagerRegistry.addManager(EnhancedConfirmationManagerLocal.class, + new EnhancedConfirmationManagerLocalImpl(this)); } + private ContentResolver mContentResolver; + private TelephonyManager mTelephonyManager; + @Override public void onStart() { Context context = getContext(); @@ -87,6 +118,8 @@ public class EnhancedConfirmationService extends SystemService { systemConfigManager.getEnhancedConfirmationTrustedInstallers()); publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub()); + mContentResolver = getContext().getContentResolver(); + mTelephonyManager = getContext().getSystemService(TelephonyManager.class); } private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) { @@ -99,6 +132,97 @@ public class EnhancedConfirmationService extends SystemService { return trustedPackageMap; } + void addOngoingCall(Call call) { + if (!Flags.unknownCallPackageInstallBlockingEnabled()) { + return; + } + if (call.getDetails() == null) { + return; + } + mOngoingCalls.put(call.getDetails().getId(), getCallType(call)); + } + + void removeOngoingCall(String callId) { + if (!Flags.unknownCallPackageInstallBlockingEnabled()) { + return; + } + Integer returned = mOngoingCalls.remove(callId); + if (returned == null) { + // TODO b/379941144: Capture a bug report whenever this happens. + } + } + + void clearOngoingCalls() { + mOngoingCalls.clear(); + } + + private @CallType int getCallType(Call call) { + String number = getPhoneNumber(call); + try { + if (number != null && mTelephonyManager.isEmergencyNumber(number)) { + return CALL_TYPE_EMERGENCY; + } + } catch (IllegalStateException | UnsupportedOperationException e) { + // If either of these are thrown, the telephony service is not available on the current + // device, either because the device lacks telephony calling, or the telephony service + // is unavailable. + } + if (number != null) { + return hasContactWithPhoneNumber(number) ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED; + } else { + return hasContactWithDisplayName(call.getDetails().getCallerDisplayName()) + ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED; + } + } + + private String getPhoneNumber(Call call) { + Uri handle = call.getDetails().getHandle(); + if (handle == null || handle.getScheme() == null) { + return null; + } + if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) { + return null; + } + return handle.getSchemeSpecificPart(); + } + + private boolean hasContactWithPhoneNumber(String phoneNumber) { + if (phoneNumber == null) { + return false; + } + Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, + Uri.encode(phoneNumber)); + String[] projection = new String[]{ + PhoneLookup.DISPLAY_NAME, + ContactsContract.PhoneLookup._ID + }; + try (Cursor res = mContentResolver.query(uri, projection, null, null)) { + return res != null && res.getCount() > 0; + } + } + + private boolean hasContactWithDisplayName(String displayName) { + if (displayName == null) { + return false; + } + Uri uri = ContactsContract.Data.CONTENT_URI; + String[] projection = new String[]{PhoneLookup._ID}; + String selection = StructuredName.DISPLAY_NAME + " = ?"; + String[] selectionArgs = new String[]{displayName}; + try (Cursor res = mContentResolver.query(uri, projection, selection, selectionArgs, null)) { + return res != null && res.getCount() > 0; + } + } + + private boolean hasCallOfType(@CallType int callType) { + for (int ongoingCallType : mOngoingCalls.values()) { + if (ongoingCallType == callType) { + return true; + } + } + return false; + } + private class Stub extends IEnhancedConfirmationManager.Stub { /** A map of ECM states to their corresponding app op states */ @@ -115,6 +239,10 @@ public class EnhancedConfirmationService extends SystemService { private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>(); + // Settings restricted when an untrusted call is ongoing. These must also be added to + // PROTECTED_SETTINGS + private static final ArraySet<String> UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>(); + static { // Runtime permissions PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS); @@ -135,6 +263,13 @@ public class EnhancedConfirmationService extends SystemService { // Default application roles. PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER); PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS); + + if (Flags.unknownCallPackageInstallBlockingEnabled()) { + // Requesting package installs, limited during phone calls + PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); + UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( + AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); + } } private final @NonNull Context mContext; @@ -163,8 +298,13 @@ public class EnhancedConfirmationService extends SystemService { "settingIdentifier cannot be null or empty"); try { - return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName, - userId); + if (!isSettingEcmProtected(settingIdentifier)) { + return false; + } + if (isSettingProtectedGlobally(settingIdentifier)) { + return true; + } + return isPackageEcmGuarded(packageName, userId); } catch (NameNotFoundException e) { throw new IllegalArgumentException(e); } @@ -227,8 +367,21 @@ public class EnhancedConfirmationService extends SystemService { } } + private boolean isUntrustedCallOngoing() { + if (!Flags.unknownCallPackageInstallBlockingEnabled()) { + return false; + } + + if (hasCallOfType(CALL_TYPE_EMERGENCY)) { + // If we have an emergency call, return false always. + return false; + } + return hasCallOfType(CALL_TYPE_UNTRUSTED); + } + private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, methodName, mContext); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName); } @@ -322,6 +475,7 @@ public class EnhancedConfirmationService extends SystemService { return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } + @SuppressLint("WrongConstant") private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState, @UserIdInt int userId) throws NameNotFoundException { int packageUid = getPackageUid(packageName, userId); @@ -359,6 +513,14 @@ public class EnhancedConfirmationService extends SystemService { return false; } + private boolean isSettingProtectedGlobally(@NonNull String settingIdentifier) { + if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { + return isUntrustedCallOngoing(); + } + + return false; + } + @Nullable private ApplicationInfo getApplicationInfoAsUser(@Nullable String packageName, @UserIdInt int userId) { diff --git a/service/java/com/android/permission/compat/UserHandleCompat.java b/service/java/com/android/permission/compat/UserHandleCompat.java deleted file mode 100644 index 1901aa997..000000000 --- a/service/java/com/android/permission/compat/UserHandleCompat.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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(); - } - - /** - * Get the UID from the give user ID and app ID - * - * @param userId the user ID - * @param appId the app ID - * @return the UID - */ - public static int getUid(@UserIdInt int userId, int appId) { - return UserHandle.of(userId).getUid(appId); - } -} diff --git a/service/java/com/android/permission/compat/package-info.java b/service/java/com/android/permission/compat/package-info.java deleted file mode 100644 index c89cc8eab..000000000 --- a/service/java/com/android/permission/compat/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java index 33389a88f..c69afb199 100644 --- a/service/java/com/android/permission/util/UserUtils.java +++ b/service/java/com/android/permission/util/UserUtils.java @@ -17,18 +17,21 @@ package com.android.permission.util; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.permission.internal.compat.UserHandleCompat; import com.android.internal.util.Preconditions; import com.android.modules.utils.build.SdkLevel; -import com.android.permission.compat.UserHandleCompat; import com.android.permission.flags.Flags; +import java.util.ArrayList; import java.util.List; /** Utility class to deal with Android users. */ @@ -40,11 +43,12 @@ public final class UserUtils { public static void enforceCrossUserPermission( @UserIdInt int userId, boolean allowAll, + boolean enforceForProfileGroup, @NonNull String message, @NonNull Context context) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandleCompat.getUserId(callingUid); - if (userId == callingUserId) { + if (userId == callingUserId && !enforceForProfileGroup) { return; } Preconditions.checkArgument( @@ -53,13 +57,40 @@ public final class UserUtils { "Invalid user " + userId); context.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); - if (callingUid != Process.SHELL_UID || userId < UserHandleCompat.USER_SYSTEM) { + if (callingUid != Process.SHELL_UID || userId == UserHandleCompat.USER_ALL) { return; } + + if (enforceForProfileGroup) { + DevicePolicyManager devicePolicyManager = + context.getSystemService(DevicePolicyManager.class); + if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { + // For profileGroup exclusive roles users on BYOD are free to choose personal o + // work profile app regardless of DISALLOW_DEBUGGING_FEATURES + return; + } + + Context userContext = UserUtils.getUserContext(userId, context); + List<UserHandle> profiles = getUserProfiles(userContext, true); + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + int profileId = profiles.get(i).getIdentifier(); + if (profileId == callingUserId) { + continue; + } + enforceShellRestriction(profileId, context); + } + } else { + enforceShellRestriction(userId, context); + } + } + + private static void enforceShellRestriction(int userId, @NonNull Context context) { UserManager userManager = context.getSystemService(UserManager.class); if (userManager.hasUserRestrictionForUser( UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) { - throw new SecurityException("Shell does not have permission to access user " + userId); + throw new SecurityException( + "Shell does not have permission to access user " + userId); } } @@ -81,6 +112,68 @@ public final class UserUtils { } } + /** Returns all the enabled user profiles on the device. */ + @NonNull + public static List<UserHandle> getUserProfiles(@NonNull Context context) { + return getUserProfiles(context, false); + } + + /** + * Returns all the enabled user profiles on the device + * + * @param context the {@link Context} + * @param excludePrivate {@code true} to exclude private profiles from returned list of users + */ + @NonNull + public static List<UserHandle> getUserProfiles(@NonNull Context context, + boolean excludePrivate) { + UserManager userManager = context.getSystemService(UserManager.class); + // This call requires the QUERY_USERS permission. + final long identity = Binder.clearCallingIdentity(); + try { + List<UserHandle> profiles = userManager.getUserProfiles(); + if (!excludePrivate) { + return profiles; + } + List<UserHandle> filteredProfiles = new ArrayList<>(); + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + UserHandle user = profiles.get(i); + if (!isPrivateProfile(user.getIdentifier(), context)) { + filteredProfiles.add(user); + } + } + return filteredProfiles; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + /** + * Returns the parent of a given user, or userId if it has no parent (e.g. it is the primary + * profile) + */ + @UserIdInt + public static int getProfileParentIdOrSelf(@UserIdInt int userId, @NonNull Context context) { + UserHandle profileParent = getProfileParent(userId, context); + // If profile parent userhandle is null, then original user id is the parent + return profileParent != null ? profileParent.getIdentifier() : userId; + } + + /** Returns the parent of a given user. */ + @Nullable + private static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) { + Context userContext = getUserContext(userId, context); + UserManager userManager = userContext.getSystemService(UserManager.class); + // This call requires the INTERACT_ACROSS_USERS permission. + final long identity = Binder.clearCallingIdentity(); + try { + return userManager.getProfileParent(UserHandle.of(userId)); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + /** Returns whether a given {@code userId} corresponds to a managed profile. */ public static boolean isManagedProfile(@UserIdInt int userId, @NonNull Context context) { UserManager userManager = context.getSystemService(UserManager.class); @@ -107,8 +200,7 @@ public final class UserUtils { // MANAGE_USERS, QUERY_USERS, or INTERACT_ACROSS_USERS. final long identity = Binder.clearCallingIdentity(); try { - Context userContext = context - .createContextAsUser(UserHandle.of(userId), /* flags= */ 0); + Context userContext = getUserContext(userId, context); UserManager userManager = userContext.getSystemService(UserManager.class); return userManager != null && userManager.isPrivateProfile(); } finally { @@ -141,4 +233,13 @@ public final class UserUtils { Binder.restoreCallingIdentity(identity); } } + + @NonNull + public static Context getUserContext(@UserIdInt int userId, @NonNull Context context) { + if (SdkLevel.isAtLeastS() && context.getUser().getIdentifier() == userId) { + return context; + } else { + return context.createContextAsUser(UserHandle.of(userId), 0); + } + } } diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java index 20250b4f6..5bc79efbb 100644 --- a/service/java/com/android/role/RoleService.java +++ b/service/java/com/android/role/RoleService.java @@ -43,9 +43,11 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.permission.flags.Flags; +import android.permission.internal.compat.UserHandleCompat; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -62,13 +64,16 @@ import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.modules.utils.build.SdkLevel; -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.PackageUtils; import com.android.permission.util.ThrottledRunnable; import com.android.permission.util.UserUtils; +import com.android.role.controller.model.Role; +import com.android.role.controller.model.Roles; +import com.android.role.controller.service.RoleControllerServiceImpl; +import com.android.role.controller.util.RoleFlags; import com.android.server.LocalManagerRegistry; import com.android.server.SystemService; import com.android.server.role.RoleServicePlatformHelper; @@ -77,6 +82,7 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -95,6 +101,7 @@ import java.util.concurrent.TimeoutException; @RequiresApi(Build.VERSION_CODES.S) public class RoleService extends SystemService implements RoleUserState.Callback { private static final String LOG_TAG = RoleService.class.getSimpleName(); + private static final String TRACE_TAG = RoleService.class.getSimpleName(); private static final boolean DEBUG = false; @@ -114,9 +121,24 @@ public class RoleService extends SystemService implements RoleUserState.Callback if (SdkLevel.isAtLeastV()) { defaultApplicationRoles.add(RoleManager.ROLE_WALLET); } + if (RoleFlags.isProfileGroupExclusivityAvailable()) { + defaultApplicationRoles.add( + RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY); + } DEFAULT_APPLICATION_ROLES = defaultApplicationRoles.toArray(new String[0]); } + private static final String[] TEST_ROLES; + + static { + if (RoleFlags.isProfileGroupExclusivityAvailable()) { + TEST_ROLES = + new String[] {RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY}; + } else { + TEST_ROLES = new String[0]; + } + } + @NonNull private final AppOpsManager mAppOpsManager; @@ -165,6 +187,11 @@ public class RoleService extends SystemService implements RoleUserState.Callback public RoleService(@NonNull Context context) { super(context); + if (RoleFlags.isProfileGroupExclusivityAvailable()) { + RoleControllerServiceImpl.sSetActiveUserForRoleMethod = + this::setActiveUserForRoleFromController; + } + mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class); RoleControllerManager.initializeRemoteServiceComponentName(context); @@ -176,6 +203,7 @@ public class RoleService extends SystemService implements RoleUserState.Callback registerUserRemovedReceiver(); } + // TODO(b/375029649): enforce single active user for all cross-user roles private void registerUserRemovedReceiver() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_REMOVED); @@ -259,11 +287,16 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void onUserStarting(@NonNull TargetUser user) { - if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) { - upgradeLegacyFallbackEnabledRolesIfNeeded(user); - } + Trace.beginSection(TRACE_TAG + "_onUserStarting"); + try { + if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) { + upgradeLegacyFallbackEnabledRolesIfNeeded(user); + } - maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier()); + maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier()); + } finally { + Trace.endSection(); + } } private void upgradeLegacyFallbackEnabledRolesIfNeeded(@NonNull TargetUser user) { @@ -406,6 +439,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback private void onRemoveUser(@UserIdInt int userId) { RemoteCallbackList<IOnRoleHoldersChangedListener> listeners; RoleUserState userState; + // UserManager still knows the user until ACTION_USER_REMOVED broadcasts are processed + int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext()); + List<String> activeRoleNames = null; synchronized (mLock) { mGrantDefaultRolesThrottledRunnables.remove(userId); listeners = mListeners.get(userId); @@ -413,7 +449,29 @@ public class RoleService extends SystemService implements RoleUserState.Callback mControllers.remove(userId); userState = mUserStates.get(userId); mUserStates.remove(userId); + + if (RoleFlags.isProfileGroupExclusivityAvailable() && userId != profileParentId) { + RoleUserState profileParentState = mUserStates.get(profileParentId); + activeRoleNames = profileParentState.getActiveRolesForUser(userId); + } } + if (RoleFlags.isProfileGroupExclusivityAvailable() && userId != profileParentId + && !CollectionUtils.isEmpty(activeRoleNames)) { + int activeRoleNamesSize = activeRoleNames.size(); + for (int i = 0; i < activeRoleNamesSize; i++) { + String roleName = activeRoleNames.get(i); + + // If the previous active user had a set role holder, attempt to fallback for + // the profile parent. + Log.i(LOG_TAG, "User " + userId + " removed, falling back to profile parent " + + profileParentId + " for role " + roleName); + // Use profileParentId instead of userId here, since userId is in a state of removal + // and might be excluded from UserManager#getUserHandles with excludeDying=true + setActiveUserForRoleAsUserInternal(roleName, profileParentId, 0, true, + profileParentId); + } + } + if (listeners != null) { listeners.kill(); } @@ -460,12 +518,100 @@ public class RoleService extends SystemService implements RoleUserState.Callback } } + private void enforceProfileGroupExclusiveRole(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkArgument(isProfileGroupExclusiveRole(roleName, getContext()), + roleName + " is not a profile-group exclusive role"); + } + + /** + * Returns whether the given role has profile group exclusivity + * + * @param roleName The name of role to check + * @param context The context + * @return {@code true} if the role is profile group exclusive, {@code false} otherwise + */ + private static boolean isProfileGroupExclusiveRole(String roleName, Context context) { + if (!RoleFlags.isProfileGroupExclusivityAvailable()) { + return false; + } + Role role = Roles.get(context).get(roleName); + return role != null && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP; + } + + private void setActiveUserForRoleFromController(@NonNull String roleName, @UserIdInt int userId, + @RoleManager.ManageHoldersFlags int flags) { + setActiveUserForRoleAsUserInternal(roleName, userId, flags, false, userId); + } + + private void setActiveUserForRoleAsUserInternal(@NonNull String roleName, + @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags, + boolean clearRoleHoldersForActiveUser, @UserIdInt int userId) { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "setActiveUserForRoleAsUser not available"); + enforceProfileGroupExclusiveRole(roleName); + + if (!UserUtils.isUserExistent(userId, getContext())) { + Log.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + if (!UserUtils.isUserExistent(activeUserId, getContext())) { + Log.e(LOG_TAG, "user " + activeUserId + " does not exist"); + return; + } + if (UserUtils.isPrivateProfile(activeUserId, getContext())) { + Log.e(LOG_TAG, "Cannot set private profile " + activeUserId + " as active user" + + " for role"); + return; + } + Context userContext = UserUtils.getUserContext(userId, getContext()); + List<UserHandle> profiles = UserUtils.getUserProfiles(userContext, true); + if (!profiles.contains(UserHandle.of(activeUserId))) { + Log.e(LOG_TAG, "User " + activeUserId + " is not in the same profile-group as " + + userId); + return; + } + + int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext()); + RoleUserState userState = getOrCreateUserState(profileParentId); + + if (!userState.setActiveUserForRole(roleName, activeUserId)) { + Log.i(LOG_TAG, "User " + activeUserId + " is already the active user for role"); + return; + } + + final int profilesSize = profiles.size(); + for (int i = 0; i < profilesSize; i++) { + int profilesUserId = profiles.get(i).getIdentifier(); + if (!clearRoleHoldersForActiveUser && profilesUserId == activeUserId) { + continue; + } + 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()); + } + }); + getOrCreateController(profilesUserId) + .onClearRoleHolders(roleName, flags, callback); + try { + future.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.e(LOG_TAG, "Exception while clearing role holders for non-active user: " + + profilesUserId, e); + } + } + } + private class Stub extends IRoleManager.Stub { @Override public boolean isRoleAvailableAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleAvailableAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleAvailableAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -481,7 +627,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @UserIdInt int userId) { mAppOpsManager.checkPackage(getCallingUid(), packageName); - UserUtils.enforceCrossUserPermission(userId, false, "isRoleHeldAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleHeldAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -500,8 +647,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @NonNull @Override public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getRoleHoldersAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return Collections.emptyList(); @@ -523,8 +670,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "addRoleHolderAsUser", - getContext()); + boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + enforceForProfileGroup, "addRoleHolderAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -544,8 +692,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "removeRoleHolderAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -566,8 +714,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void clearRoleHoldersAsUser(@NonNull String roleName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "clearRoleHoldersAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -585,7 +733,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override @Nullable public String getDefaultApplicationAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "getDefaultApplicationAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getDefaultApplicationAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -610,8 +759,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback public void setDefaultApplicationAsUser(@NonNull String roleName, @Nullable String packageName, @RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId, @NonNull RemoteCallback callback) { - UserUtils.enforceCrossUserPermission(userId, false, "setDefaultApplicationAsUser", - getContext()); + boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + enforceForProfileGroup, "setDefaultApplicationAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return; @@ -633,10 +783,59 @@ public class RoleService extends SystemService implements RoleUserState.Callback } @Override + public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) { + Trace.beginSection(TRACE_TAG + "_getActiveUserForRoleAsUser"); + try { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "getActiveUserForRoleAsUser not available"); + enforceProfileGroupExclusiveRole(roleName); + + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ true, "getActiveUserForRole", getContext()); + if (!UserUtils.isUserExistent(userId, getContext())) { + Log.e(LOG_TAG, "user " + userId + " does not exist"); + return UserHandleCompat.USER_NULL; + } + + enforceCallingOrSelfAnyPermissions(new String[] { + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS, + Manifest.permission.MANAGE_ROLE_HOLDERS + }, "getActiveUserForRole"); + + int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext()); + RoleUserState userState = getOrCreateUserState(profileParentId); + return userState.getActiveUserForRole(roleName); + } finally { + Trace.endSection(); + } + } + + @Override + public void setActiveUserForRoleAsUser(@NonNull String roleName, + @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags, + @UserIdInt int userId) { + Trace.beginSection(TRACE_TAG + "_setActiveUserForRoleAsUser"); + try { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "setActiveUserForRoleAsUser not available"); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ true, "setActiveUserForRole", getContext()); + enforceCallingOrSelfAnyPermissions(new String[] { + Manifest.permission.MANAGE_DEFAULT_APPLICATIONS, + Manifest.permission.MANAGE_ROLE_HOLDERS + }, "setActiveUserForRoleAsUser"); + setActiveUserForRoleAsUserInternal(roleName, activeUserId, flags, true, userId); + } finally { + Trace.endSection(); + } + } + + @Override public void addOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, true, - "addOnRoleHoldersChangedListenerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true, + /* enforceForProfileGroup= */ false, "addOnRoleHoldersChangedListenerAsUser", + getContext()); if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -656,7 +855,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void removeOnRoleHoldersChangedListenerAsUser( @NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, true, + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true, + /* enforceForProfileGroup= */ false, "removeOnRoleHoldersChangedListenerAsUser", getContext()); if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId, getContext())) { @@ -709,7 +909,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isRoleFallbackEnabledAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleFallbackEnabledAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleFallbackEnabledAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -727,7 +928,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void setRoleFallbackEnabledAsUser(@NonNull String roleName, boolean fallbackEnabled, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "setRoleFallbackEnabledAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "setRoleFallbackEnabledAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -745,7 +947,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public void setRoleNamesFromControllerAsUser(@NonNull List<String> roleNames, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "setRoleNamesFromControllerAsUser", + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "setRoleNamesFromControllerAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); @@ -764,8 +967,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean addRoleHolderFromControllerAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "addRoleHolderFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "addRoleHolderFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -784,8 +988,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean removeRoleHolderFromControllerAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "removeRoleHolderFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "removeRoleHolderFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -804,8 +1009,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public List<String> getHeldRolesFromControllerAsUser(@NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "getHeldRolesFromControllerAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getHeldRolesFromControllerAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return Collections.emptyList(); @@ -914,7 +1120,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public String getSmsRoleHolder(int userId) { final Context context = getContext(); - UserUtils.enforceCrossUserPermission(userId, false, "getSmsRoleHolder", context); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getSmsRoleHolder", context); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return null; @@ -938,7 +1145,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public String getEmergencyRoleHolder(int userId) { final Context context = getContext(); - UserUtils.enforceCrossUserPermission(userId, false, "getEmergencyRoleHolder", context); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "getEmergencyRoleHolder", context); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return null; @@ -964,8 +1172,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isRoleVisibleAsUser(@NonNull String roleName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, "isRoleVisibleAsUser", - getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isRoleVisibleAsUser", getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -982,8 +1190,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback @Override public boolean isApplicationVisibleForRoleAsUser(@NonNull String roleName, @NonNull String packageName, @UserIdInt int userId) { - UserUtils.enforceCrossUserPermission(userId, false, - "isApplicationVisibleForRoleAsUser", getContext()); + UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false, + /* enforceForProfileGroup= */ false, "isApplicationVisibleForRoleAsUser", + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); return false; @@ -998,6 +1207,59 @@ public class RoleService extends SystemService implements RoleUserState.Callback return getOrCreateController(userId).isApplicationVisibleForRole(roleName, packageName); } + @NonNull + @Override + public List<String> getDefaultHoldersForTestAsUser(@NonNull String roleName, + @UserIdInt int userId) { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "getDefaultHoldersForTest not available"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "getDefaultHoldersForTest"); + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName); + + return getOrCreateUserState(userId).getDefaultHoldersForTest(roleName); + } + + @Override + public void setDefaultHoldersForTestAsUser(@NonNull String roleName, + @NonNull List<String> packageNames, @UserIdInt int userId) { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "setDefaultHoldersForTest not available"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "setDefaultHoldersForTest"); + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName); + Objects.requireNonNull(packageNames, "packageNames cannot be null"); + + getOrCreateUserState(userId).setDefaultHoldersForTest(roleName, packageNames); + } + + @Override + public boolean isRoleVisibleForTestAsUser(@NonNull String roleName, @UserIdInt int userId) { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "isRoleVisibleForTest not available"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "isRoleVisibleForTest"); + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName); + + return getOrCreateUserState(userId).isRoleVisibleForTest(roleName); + } + + @Override + public void setRoleVisibleForTestAsUser(@NonNull String roleName, boolean visible, + @UserIdInt int userId) { + Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(), + "setRoleVisibleForTest not available"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "setRoleVisibleForTest"); + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName); + + getOrCreateUserState(userId).setRoleVisibleForTest(roleName, visible); + } + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { @@ -1040,6 +1302,20 @@ public class RoleService extends SystemService implements RoleUserState.Callback return true; } } + + private void enforceCallingOrSelfAnyPermissions(@NonNull String[] permissions, + @NonNull String message) { + for (String permission : permissions) { + if (getContext().checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + + throw new SecurityException(message + ": Neither user " + Binder.getCallingUid() + + " nor current process has at least one of" + Arrays.toString(permissions) + + "."); + } } private class Local implements RoleManagerLocal { diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java index 808a64cb4..538e62f47 100644 --- a/service/java/com/android/role/RoleShellCommand.java +++ b/service/java/com/android/role/RoleShellCommand.java @@ -22,11 +22,11 @@ import android.app.role.IRoleManager; import android.os.Build; import android.os.RemoteCallback; import android.os.RemoteException; +import android.permission.internal.compat.UserHandleCompat; import androidx.annotation.RequiresApi; import com.android.modules.utils.BasicShellCommandHandler; -import com.android.permission.compat.UserHandleCompat; import java.io.PrintWriter; import java.util.List; @@ -87,6 +87,10 @@ class RoleShellCommand extends BasicShellCommandHandler { return runClearRoleHolders(); case "set-bypassing-role-qualification": return runSetBypassingRoleQualification(); + case "get-active-user-for-role": + return runGetActiveUserForRole(); + case "set-active-user-for-role": + return runSetActiveUserForRole(); default: return handleDefaultCommands(cmd); } @@ -162,6 +166,27 @@ class RoleShellCommand extends BasicShellCommandHandler { return 0; } + private int runGetActiveUserForRole() throws RemoteException { + int userId = getUserIdMaybe(); + String roleName = getNextArgRequired(); + + int activeUserId = mRoleManager.getActiveUserForRoleAsUser(roleName, userId); + if (activeUserId != UserHandleCompat.USER_NULL) { + getOutPrintWriter().println(activeUserId); + } + return 0; + } + + private int runSetActiveUserForRole() throws RemoteException { + int userId = getUserIdMaybe(); + String roleName = getNextArgRequired(); + int activeUserId = Integer.parseInt(getNextArgRequired()); + int flags = getFlagsMaybe(); + + mRoleManager.setActiveUserForRoleAsUser(roleName, activeUserId, flags, userId); + return 0; + } + @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -174,6 +199,8 @@ class RoleShellCommand extends BasicShellCommandHandler { pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]"); pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]"); pw.println(" set-bypassing-role-qualification true|false"); + pw.println(" get-active-user-for-role [--user USER_ID] ROLE"); + pw.println(" set-active-user-for-role [--user USER_ID] ROLE ACTIVE_USER_ID [FLAGS]"); pw.println(); } } diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java index 81007d65e..18574a6fc 100644 --- a/service/java/com/android/role/RoleUserState.java +++ b/service/java/com/android/role/RoleUserState.java @@ -24,6 +24,7 @@ import android.annotation.WorkerThread; import android.os.Build; import android.os.Handler; import android.os.UserHandle; +import android.permission.internal.compat.UserHandleCompat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -39,6 +40,7 @@ import com.android.role.persistence.RolesState; import com.android.server.role.RoleServicePlatformHelper; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; @@ -96,6 +98,18 @@ class RoleUserState { private ArraySet<String> mFallbackEnabledRoles = new ArraySet<>(); @GuardedBy("mLock") + @NonNull + private ArrayMap<String, Integer> mActiveUserIds = new ArrayMap<>(); + + @GuardedBy("mLock") + @NonNull + private final Map<String, List<String>> mDefaultHoldersForTest = new ArrayMap<>(); + + @GuardedBy("mLock") + @NonNull + private final Set<String> mRolesVisibleForTest = new ArraySet<>(); + + @GuardedBy("mLock") private boolean mWriteScheduled; @GuardedBy("mLock") @@ -423,6 +437,88 @@ class RoleUserState { } /** + * Return the active user for the role + * + * @param roleName the name of the role to get the active user for + */ + public int getActiveUserForRole(@NonNull String roleName) { + synchronized (mLock) { + return mActiveUserIds.getOrDefault(roleName, UserHandleCompat.USER_NULL); + } + } + + @NonNull + public List<String> getActiveRolesForUser(@UserIdInt int userId) { + synchronized (mLock) { + List<String> activeRoleNames = new ArrayList<>(); + int activeUserIdsSize = mActiveUserIds.size(); + for (int i = 0; i < activeUserIdsSize; i++) { + int activeUserId = mActiveUserIds.valueAt(i); + if (activeUserId == userId) { + String roleName = mActiveUserIds.keyAt(i); + activeRoleNames.add(roleName); + } + } + return activeRoleNames; + } + } + + /** + * Set the active user for the role + * + * @param roleName the name of the role to set the active user for + * @param userId User id to set as active for this role + * @return whether any changes were made + */ + public boolean setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId) { + if (!com.android.permission.flags.Flags.crossUserRoleEnabled()) { + return false; + } + synchronized (mLock) { + Integer currentActiveUserId = mActiveUserIds.get(roleName); + // If we have pre-existing roles that weren't profile group exclusive and don't have an + // active user, ensure we set and write value, and return modified, otherwise other + // users might not have role holder revoked. + if (currentActiveUserId != null && currentActiveUserId == userId) { + return false; + } + mActiveUserIds.put(roleName, userId); + scheduleWriteFileLocked(); + return true; + } + } + + @NonNull + public List<String> getDefaultHoldersForTest(@NonNull String roleName) { + synchronized (mLock) { + return mDefaultHoldersForTest.getOrDefault(roleName, Collections.emptyList()); + } + } + + public void setDefaultHoldersForTest(@NonNull String roleName, + @NonNull List<String> packageNames) { + synchronized (mLock) { + mDefaultHoldersForTest.put(roleName, packageNames); + } + } + + public boolean isRoleVisibleForTest(@NonNull String roleName) { + synchronized (mLock) { + return mRolesVisibleForTest.contains(roleName); + } + } + + public void setRoleVisibleForTest(@NonNull String roleName, boolean visible) { + synchronized (mLock) { + if (visible) { + mRolesVisibleForTest.add(roleName); + } else { + mRolesVisibleForTest.remove(roleName); + } + } + } + + /** * Schedule writing the state to file. */ @GuardedBy("mLock") @@ -449,9 +545,15 @@ class RoleUserState { // Force a reconciliation on next boot if we are bypassing role qualification now. String packagesHash = mBypassingRoleQualification ? null : mPackagesHash; - roles = new RolesState(mVersion, packagesHash, - (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(), - snapshotFallbackEnabledRoles()); + if (com.android.permission.flags.Flags.crossUserRoleEnabled()) { + roles = new RolesState(mVersion, packagesHash, + (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(), + snapshotFallbackEnabledRoles(), snapshotActiveUserIds()); + } else { + roles = new RolesState(mVersion, packagesHash, + (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(), + snapshotFallbackEnabledRoles()); + } } mPersistence.writeForUser(roles, UserHandle.of(mUserId)); @@ -463,14 +565,17 @@ class RoleUserState { Map<String, Set<String>> roles; Set<String> fallbackEnabledRoles; + Map<String, Integer> activeUserIds; if (roleState != null) { mVersion = roleState.getVersion(); mPackagesHash = roleState.getPackagesHash(); roles = roleState.getRoles(); fallbackEnabledRoles = roleState.getFallbackEnabledRoles(); + activeUserIds = roleState.getActiveUserIds(); } else { roles = mPlatformHelper.getLegacyRoleState(mUserId); fallbackEnabledRoles = roles.keySet(); + activeUserIds = Collections.emptyMap(); } mRoles.clear(); for (Map.Entry<String, Set<String>> entry : roles.entrySet()) { @@ -480,6 +585,10 @@ class RoleUserState { } mFallbackEnabledRoles.clear(); mFallbackEnabledRoles.addAll(fallbackEnabledRoles); + mActiveUserIds.clear(); + if (com.android.permission.flags.Flags.crossUserRoleEnabled()) { + mActiveUserIds.putAll(activeUserIds); + } if (roleState == null) { scheduleWriteFileLocked(); } @@ -496,12 +605,14 @@ class RoleUserState { int version; String packagesHash; ArrayMap<String, ArraySet<String>> roles; + ArrayMap<String, Integer> activeUserIds; ArraySet<String> fallbackEnabledRoles; synchronized (mLock) { version = mVersion; packagesHash = mPackagesHash; roles = snapshotRolesLocked(); fallbackEnabledRoles = snapshotFallbackEnabledRoles(); + activeUserIds = snapshotActiveUserIds(); } long fieldToken = dumpOutputStream.start(fieldName, fieldId); @@ -514,10 +625,14 @@ class RoleUserState { String roleName = roles.keyAt(rolesIndex); ArraySet<String> roleHolders = roles.valueAt(rolesIndex); boolean fallbackEnabled = fallbackEnabledRoles.contains(roleName); + Integer activeUserId = activeUserIds.get(roleName); long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES); dumpOutputStream.write("name", RoleProto.NAME, roleName); dumpOutputStream.write("fallback_enabled", RoleProto.FALLBACK_ENABLED, fallbackEnabled); + if (activeUserId != null) { + dumpOutputStream.write("active_user_id", RoleProto.ACTIVE_USER_ID, activeUserId); + } int roleHoldersSize = roleHolders.size(); for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) { String roleHolder = roleHolders.valueAt(roleHoldersIndex); @@ -563,6 +678,12 @@ class RoleUserState { return new ArraySet<>(mFallbackEnabledRoles); } + @GuardedBy("mLock") + @NonNull + private ArrayMap<String, Integer> snapshotActiveUserIds() { + return new ArrayMap<>(mActiveUserIds); + } + /** * 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. diff --git a/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/service/java/com/android/role/persistence/RolesPersistenceImpl.java index 220a8440b..8382d3632 100644 --- a/service/java/com/android/role/persistence/RolesPersistenceImpl.java +++ b/service/java/com/android/role/persistence/RolesPersistenceImpl.java @@ -67,6 +67,7 @@ public class RolesPersistenceImpl implements RolesPersistence { private static final String ATTRIBUTE_VERSION = "version"; private static final String ATTRIBUTE_NAME = "name"; private static final String ATTRIBUTE_FALLBACK_ENABLED = "fallbackEnabled"; + private static final String ATTRIBUTE_ACTIVE_USER_ID = "activeUserId"; private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash"; @VisibleForTesting @@ -144,6 +145,7 @@ public class RolesPersistenceImpl implements RolesPersistence { Map<String, Set<String>> roles = new ArrayMap<>(); Set<String> fallbackEnabledRoles = new ArraySet<>(); + Map<String, Integer> activeUserIds = new ArrayMap<>(); int type; int depth; int innerDepth = parser.getDepth() + 1; @@ -159,12 +161,23 @@ public class RolesPersistenceImpl implements RolesPersistence { if (Boolean.parseBoolean(fallbackEnabled)) { fallbackEnabledRoles.add(roleName); } + if (com.android.permission.flags.Flags.crossUserRoleEnabled()) { + String activeUserId = parser.getAttributeValue(null, ATTRIBUTE_ACTIVE_USER_ID); + if (activeUserId != null) { + activeUserIds.put(roleName, Integer.parseInt(activeUserId)); + } + } Set<String> roleHolders = parseRoleHolders(parser); roles.put(roleName, roleHolders); } } - return new RolesState(version, packagesHash, roles, fallbackEnabledRoles); + if (com.android.permission.flags.Flags.crossUserRoleEnabled()) { + return new RolesState(version, packagesHash, roles, fallbackEnabledRoles, + activeUserIds); + } else { + return new RolesState(version, packagesHash, roles, fallbackEnabledRoles); + } } @NonNull @@ -244,15 +257,22 @@ public class RolesPersistenceImpl implements RolesPersistence { } Set<String> fallbackEnabledRoles = roles.getFallbackEnabledRoles(); + Map<String, Integer> activeUserIds = roles.getActiveUserIds(); for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) { String roleName = entry.getKey(); Set<String> roleHolders = entry.getValue(); boolean isFallbackEnabled = fallbackEnabledRoles.contains(roleName); + Integer activeUserId = com.android.permission.flags.Flags.crossUserRoleEnabled() + ? activeUserIds.get(roleName) : null; serializer.startTag(null, TAG_ROLE); serializer.attribute(null, ATTRIBUTE_NAME, roleName); serializer.attribute(null, ATTRIBUTE_FALLBACK_ENABLED, Boolean.toString(isFallbackEnabled)); + if (activeUserId != null) { + serializer.attribute( + null, ATTRIBUTE_ACTIVE_USER_ID, Integer.toString(activeUserId)); + } serializeRoleHolders(serializer, roleHolders); serializer.endTag(null, TAG_ROLE); } diff --git a/service/java/com/android/role/persistence/RolesState.java b/service/java/com/android/role/persistence/RolesState.java index a189dd4c2..f1b3d8dfa 100644 --- a/service/java/com/android/role/persistence/RolesState.java +++ b/service/java/com/android/role/persistence/RolesState.java @@ -23,12 +23,13 @@ import android.annotation.SystemApi; import android.annotation.SystemApi.Client; import android.permission.flags.Flags; +import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Set; /** - * State of all roles. + * State of all roles for a user. * * TODO(b/147914847): Remove @hide when it becomes the default. * @hide @@ -59,6 +60,12 @@ public final class RolesState { private final Set<String> mFallbackEnabledRoles; /** + * The active users for cross user roles. + */ + @NonNull + private final Map<String, Integer> mActiveUserIds; + + /** * Create a new instance of this class. * * @param version the version of the roles @@ -81,10 +88,27 @@ public final class RolesState { @FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) public RolesState(int version, @Nullable String packagesHash, @NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles) { + this(version, packagesHash, roles, fallbackEnabledRoles, Collections.emptyMap()); + } + + /** + * Create a new instance of this class. + * + * @param version the version of the roles + * @param packagesHash the hash of all packages in the system + * @param roles the roles + * @param fallbackEnabledRoles the roles with fallback enabled + * @param activeUserIds the active users for cross user roles + * @hide + */ + public RolesState(int version, @Nullable String packagesHash, + @NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles, + @NonNull Map<String, Integer> activeUserIds) { mVersion = version; mPackagesHash = packagesHash; mRoles = roles; mFallbackEnabledRoles = fallbackEnabledRoles; + mActiveUserIds = activeUserIds; } /** @@ -127,6 +151,17 @@ public final class RolesState { return mFallbackEnabledRoles; } + /** + * Get the active users for cross user roles. + * + * @return active users for cross user roles + * @hide + */ + @NonNull + public Map<String, Integer> getActiveUserIds() { + return mActiveUserIds; + } + @Override public boolean equals(Object object) { if (this == object) { @@ -139,11 +174,12 @@ public final class RolesState { return mVersion == that.mVersion && Objects.equals(mPackagesHash, that.mPackagesHash) && Objects.equals(mRoles, that.mRoles) - && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles); + && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles) + && Objects.equals(mActiveUserIds, that.mActiveUserIds); } @Override public int hashCode() { - return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles); + return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles, mActiveUserIds); } } diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index 250be5f25..6df4184e1 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -694,7 +694,8 @@ public final class SafetyCenterService extends SystemService { /** Enforces cross user permission and returns whether the user is valid. */ private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) { UserUtils.enforceCrossUserPermission( - userId, /* allowAll= */ false, message, getContext()); + userId, /* allowAll= */ false, /* enforceForProfileGroup= */ false, message, + getContext()); if (!UserUtils.isUserExistent(userId, getContext())) { Log.w( TAG, diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java index 46a440bf7..1f5258437 100644 --- a/service/java/com/android/safetycenter/UserProfileGroup.java +++ b/service/java/com/android/safetycenter/UserProfileGroup.java @@ -21,15 +21,12 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.UserIdInt; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Binder; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; +import android.permission.internal.compat.UserHandleCompat; import android.util.Log; -import androidx.annotation.Nullable; - import com.android.permission.util.UserUtils; import java.lang.annotation.Retention; @@ -49,8 +46,6 @@ import java.util.Objects; public final class UserProfileGroup { private static final String TAG = "UserProfileGroup"; - // UserHandle#USER_NULL is a @TestApi so it cannot be accessed from the mainline module. - public static final @UserIdInt int USER_NULL = -10000; @UserIdInt private final int mProfileParentUserId; private final int[] mManagedProfilesUserIds; @@ -134,20 +129,16 @@ public final class UserProfileGroup { * is disabled. */ public static UserProfileGroup fromUser(Context context, @UserIdInt int userId) { - UserManager userManager = getUserManagerForUser(userId, context); - List<UserHandle> userProfiles = getEnabledUserProfiles(userManager); - UserHandle profileParent = getProfileParent(userManager, userId); - int profileParentUserId = userId; - if (profileParent != null) { - profileParentUserId = profileParent.getIdentifier(); - } + Context userContext = UserUtils.getUserContext(userId, context); + List<UserHandle> userProfiles = UserUtils.getUserProfiles(userContext); + int profileParentUserId = UserUtils.getProfileParentIdOrSelf(userId, userContext); int[] managedProfilesUserIds = new int[userProfiles.size()]; int[] managedRunningProfilesUserIds = new int[userProfiles.size()]; int managedProfilesUserIdsLen = 0; int managedRunningProfilesUserIdsLen = 0; - int privateProfileUserId = USER_NULL; + int privateProfileUserId = UserHandleCompat.USER_NULL; boolean privateProfileRunning = false; for (int i = 0; i < userProfiles.size(); i++) { @@ -192,23 +183,10 @@ public final class UserProfileGroup { } private static UserManager getUserManagerForUser(@UserIdInt int userId, Context context) { - Context userContext = getUserContext(context, UserHandle.of(userId)); + Context userContext = UserUtils.getUserContext(userId, context); return requireNonNull(userContext.getSystemService(UserManager.class)); } - private static Context getUserContext(Context context, UserHandle userHandle) { - if (Process.myUserHandle().equals(userHandle)) { - return context; - } else { - try { - return context.createPackageContextAsUser( - context.getPackageName(), /* flags= */ 0, userHandle); - } catch (PackageManager.NameNotFoundException doesNotHappen) { - throw new IllegalStateException(doesNotHappen); - } - } - } - private static boolean isProfile(@UserIdInt int userId, Context context) { // This call requires the INTERACT_ACROSS_USERS permission. final long callingId = Binder.clearCallingIdentity(); @@ -220,27 +198,6 @@ public final class UserProfileGroup { } } - private static List<UserHandle> getEnabledUserProfiles(UserManager userManager) { - // This call requires the QUERY_USERS permission. - final long callingId = Binder.clearCallingIdentity(); - try { - return userManager.getUserProfiles(); - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - @Nullable - private static UserHandle getProfileParent(UserManager userManager, @UserIdInt int userId) { - // This call requires the INTERACT_ACROSS_USERS permission. - final long callingId = Binder.clearCallingIdentity(); - try { - return userManager.getProfileParent(UserHandle.of(userId)); - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - /** Returns the profile parent user id of the {@link UserProfileGroup}. */ public int getProfileParentUserId() { return mProfileParentUserId; @@ -262,7 +219,7 @@ public final class UserProfileGroup { /* destPos= */ 1, mManagedProfilesUserIds.length); - if (mPrivateProfileUserId != USER_NULL) { + if (mPrivateProfileUserId != UserHandleCompat.USER_NULL) { allProfileIds[allProfileIds.length - 1] = mPrivateProfileUserId; } @@ -303,7 +260,7 @@ public final class UserProfileGroup { case PROFILE_TYPE_MANAGED: return mManagedProfilesUserIds; case PROFILE_TYPE_PRIVATE: - return mPrivateProfileUserId != USER_NULL + return mPrivateProfileUserId != UserHandleCompat.USER_NULL ? new int[]{mPrivateProfileUserId} : new int[]{}; default: Log.w(TAG, "profiles requested for unexpected profile type " + profileType); @@ -342,7 +299,7 @@ public final class UserProfileGroup { private int getNumProfiles() { return 1 + mManagedProfilesUserIds.length - + (mPrivateProfileUserId == USER_NULL ? 0 : 1); + + (mPrivateProfileUserId == UserHandleCompat.USER_NULL ? 0 : 1); } /** @@ -395,7 +352,8 @@ public final class UserProfileGroup { } } - return USER_NULL != mPrivateProfileUserId && userId == mPrivateProfileUserId; + return UserHandleCompat.USER_NULL != mPrivateProfileUserId + && userId == mPrivateProfileUserId; } @Override diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml index b10928320..6aaf3da8c 100644 --- a/service/lint-baseline.xml +++ b/service/lint-baseline.xml @@ -35,17 +35,6 @@ </issue> <issue - id="NewApi" - message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`" - errorLine1=" return UserHandle.of(userId).getUid(appId);" - errorLine2=" ~~~~~~"> - <location - file="packages/modules/Permission/service/java/com/android/permission/compat/UserHandleCompat.java" - line="57" - column="38"/> - </issue> - - <issue id="FlaggedApi" message="Method `RolesState()` is a flagged API and should be inside an `if (Flags.systemServerRoleControllerEnabled())` check (or annotate the surrounding method `writeFile` with `@FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) to transfer requirement to caller`)" errorLine1=" roles = new RolesState(mVersion, packagesHash," diff --git a/service/proto/role_service.proto b/service/proto/role_service.proto index f982ead5b..fb1866d83 100644 --- a/service/proto/role_service.proto +++ b/service/proto/role_service.proto @@ -56,4 +56,7 @@ message RoleProto { // Whether fallback holders are enabled for this role. optional bool fallback_enabled = 3; + + // The active user id of this cross user role. + optional int32 active_user_id = 4; } |