diff options
Diffstat (limited to 'service')
15 files changed, 697 insertions, 155 deletions
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..ef6971b11 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -10,6 +10,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 diff --git a/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java new file mode 100644 index 000000000..407d56f70 --- /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.enhancedConfirmationInCallApisEnabled()) { + 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..a5c6d3c36 --- /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.enhancedConfirmationInCallApisEnabled()) { + 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..dde9fe2fd 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.enhancedConfirmationInCallApisEnabled()) { + return; + } + if (call.getDetails() == null) { + return; + } + mOngoingCalls.put(call.getDetails().getId(), getCallType(call)); + } + + void removeOngoingCall(String callId) { + if (!Flags.enhancedConfirmationInCallApisEnabled()) { + 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 */ @@ -227,8 +351,20 @@ public class EnhancedConfirmationService extends SystemService { } } + @Override + public boolean isUntrustedCallOngoing() { + enforcePermissions("isUntrustedCallOngoing", + UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier()); + 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 +458,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); 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 1b3ebb8d6..000000000 --- a/service/java/com/android/permission/compat/UserHandleCompat.java +++ /dev/null @@ -1,66 +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 an undefined user of the device. - * - * @see UserHandle#USER_NULL - */ - public static final @UserIdInt int USER_NULL = -10000; - - /** - * 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 639c7aacb..c69afb199 100644 --- a/service/java/com/android/permission/util/UserUtils.java +++ b/service/java/com/android/permission/util/UserUtils.java @@ -17,21 +17,22 @@ 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.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 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; -import java.util.Objects; /** Utility class to deal with Android users. */ public final class UserUtils { @@ -42,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( @@ -55,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); } } @@ -86,18 +115,54 @@ 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 { - return userManager.getUserProfiles(); + 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. */ - public static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) { + @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. diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java index b8e3ad8b1..7c2ab01b1 100644 --- a/service/java/com/android/role/RoleService.java +++ b/service/java/com/android/role/RoleService.java @@ -46,6 +46,7 @@ import android.os.RemoteException; 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 +63,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 +81,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; @@ -114,6 +119,10 @@ 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]); } @@ -165,6 +174,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); @@ -462,12 +476,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; @@ -483,7 +585,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; @@ -502,8 +605,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(); @@ -525,8 +628,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; @@ -546,8 +650,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; @@ -568,8 +672,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; @@ -587,7 +691,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"); @@ -612,8 +717,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; @@ -635,10 +741,49 @@ public class RoleService extends SystemService implements RoleUserState.Callback } @Override + public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) { + 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); + } + + @Override + public void setActiveUserForRoleAsUser(@NonNull String roleName, + @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags, + @UserIdInt int userId) { + 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); + } + + @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"); @@ -658,7 +803,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())) { @@ -711,7 +857,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"); @@ -729,7 +876,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"); @@ -747,7 +895,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"); @@ -766,8 +915,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; @@ -786,8 +936,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; @@ -806,8 +957,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(); @@ -916,7 +1068,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; @@ -940,7 +1093,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; @@ -966,8 +1120,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; @@ -984,8 +1138,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; @@ -1042,6 +1197,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 cda7fcfa8..c94b58826 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; @@ -428,6 +429,42 @@ 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); + } + } + + /** + * 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; + } + } + + /** * Schedule writing the state to file. */ @GuardedBy("mLock") 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 a78113b04..1f5258437 100644 --- a/service/java/com/android/safetycenter/UserProfileGroup.java +++ b/service/java/com/android/safetycenter/UserProfileGroup.java @@ -21,16 +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.compat.UserHandleCompat; import com.android.permission.util.UserUtils; import java.lang.annotation.Retention; @@ -135,11 +131,7 @@ public final class UserProfileGroup { public static UserProfileGroup fromUser(Context context, @UserIdInt int userId) { Context userContext = UserUtils.getUserContext(userId, context); List<UserHandle> userProfiles = UserUtils.getUserProfiles(userContext); - UserHandle profileParent = UserUtils.getProfileParent(userId, userContext); - int profileParentUserId = userId; - if (profileParent != null) { - profileParentUserId = profileParent.getIdentifier(); - } + int profileParentUserId = UserUtils.getProfileParentIdOrSelf(userId, userContext); int[] managedProfilesUserIds = new int[userProfiles.size()]; int[] managedRunningProfilesUserIds = new int[userProfiles.size()]; 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," |