diff options
13 files changed, 428 insertions, 134 deletions
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java index 170c42c3d..3e209aaee 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java @@ -16,12 +16,11 @@ package com.android.role.controller.behavior; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.nfc.NfcAdapter; -import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.CardEmulation; import android.nfc.cardemulation.HostApduService; import android.nfc.cardemulation.OffHostApduService; @@ -30,7 +29,6 @@ import android.os.UserHandle; import android.permission.flags.Flags; import android.service.quickaccesswallet.QuickAccessWalletService; import android.util.ArraySet; -import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -65,21 +63,13 @@ public class WalletRoleBehavior implements RoleBehavior { @Override public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { - CardEmulation cardEmulation; Context userContext = UserUtils.getUserContext(context, user); - try { - cardEmulation = - CardEmulation.getInstance(NfcAdapter.getDefaultAdapter(userContext)); - } catch (UnsupportedOperationException e) { - Log.e(LOG_TAG, "Unsupported Card Emulation Operation.", e); - return null; - } - ApduServiceInfo preferredPaymentService = cardEmulation - .getPreferredPaymentService(); + ComponentName preferredPaymentService = + CardEmulation.getPreferredPaymentService(userContext); if (preferredPaymentService != null) { - return Collections.singletonList(preferredPaymentService.getComponent() - .getPackageName()); + return Collections.singletonList(preferredPaymentService.getPackageName()); } + return null; } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java index 84d019bad..24b6439b5 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java @@ -460,7 +460,7 @@ public final class AppPermissionGroupsFragment extends SettingsWithLargeHeader i } private boolean isArchivingEnabled() { - return SdkLevel.isAtLeastV() && Flags.archiving(); + return SdkLevel.isAtLeastV() && Flags.archivingReadOnly(); } private void setAutoRevokeToggleState(HibernationSettingState state) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt index 3786b99bd..91b94da9a 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -171,7 +171,7 @@ class GrantPermissionsViewModel( private var appPermGroupLiveDatas = mutableMapOf<String, LightAppPermGroupLiveData>() - internal data class ResultCallback(val consumer: Consumer<Intent>, val requestCode: Int) + internal data class ResultCallback(val consumer: Consumer<Intent?>, val requestCode: Int) private var activityResultCallback: ResultCallback? = null diff --git a/flags/flags.aconfig b/flags/flags.aconfig index 8c5f2dd53..5d9e01052 100644 --- a/flags/flags.aconfig +++ b/flags/flags.aconfig @@ -15,8 +15,9 @@ flag { } flag { - name: "archiving" + name: "archiving_read_only" namespace: "permissions" description: "Feature flag to enable the archiving feature." bug: "278553670" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index e8d1b8992..02ab5b562 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -2,10 +2,10 @@ package android.app.ecm { @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public final class EnhancedConfirmationManager { - method public void clearRestriction(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void clearRestriction(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; method @NonNull public android.app.PendingIntent getRestrictedSettingDialogIntent(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method public boolean isClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - method public boolean isRestricted(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; + method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isRestricted(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; } } diff --git a/framework-s/jarjar-rules.txt b/framework-s/jarjar-rules.txt index 39f2ad3b7..22e1da3ac 100644 --- a/framework-s/jarjar-rules.txt +++ b/framework-s/jarjar-rules.txt @@ -1,5 +1,6 @@ rule android.os.HandlerExecutor android.permission.jarjar.@0 -rule android.permission.flags.** android.permission.jarjar.@0 +rule android.permission.flags.*Flags* android.permission.jarjar.@0 +rule android.permission.flags.Flags android.permission.jarjar.@0 rule android.util.IndentingPrintWriter android.permission.jarjar.@0 rule com.android.internal.** android.permission.jarjar.@0 rule com.android.modules.** android.permission.jarjar.@0 diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java b/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java index b4c18573a..1a42f7ee2 100644 --- a/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java +++ b/framework-s/java/android/app/ecm/EnhancedConfirmationFrameworkInitializer.java @@ -45,6 +45,7 @@ public class EnhancedConfirmationFrameworkInitializer { public static void registerServiceWrappers() { SystemServiceRegistry.registerContextAwareService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, EnhancedConfirmationManager.class, - (context) -> new EnhancedConfirmationManager(context)); + (context, serviceBinder) -> new EnhancedConfirmationManager(context, + IEnhancedConfirmationManager.Stub.asInterface(serviceBinder))); } } diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java index 4f1edb092..6feae2ee8 100644 --- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java +++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java @@ -18,6 +18,7 @@ package android.app.ecm; import android.annotation.FlaggedApi; import android.annotation.IntDef; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TargetApi; @@ -25,17 +26,13 @@ import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.InstallSourceInfo; -import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; -import android.os.UserHandle; +import android.os.RemoteException; import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArraySet; -import android.util.Log; import androidx.annotation.NonNull; @@ -217,20 +214,18 @@ public final class EnhancedConfirmationManager { } private final @NonNull Context mContext; - private final String mAttributionTag; - private final @NonNull UserHandle mUser; - private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; + private final @NonNull IEnhancedConfirmationManager mService; + /** * @hide */ - public EnhancedConfirmationManager(@NonNull Context context) { + public EnhancedConfirmationManager(@NonNull Context context, + @NonNull IEnhancedConfirmationManager service) { mContext = context; - mAttributionTag = context.getAttributionTag(); - mUser = context.getUser(); - mAppOpsManager = context.getSystemService(AppOpsManager.class); mPackageManager = context.getPackageManager(); + mService = service; } /** @@ -245,9 +240,17 @@ public final class EnhancedConfirmationManager { * @return {@code true} if the setting is restricted from the app * @throws NameNotFoundException if the provided package was not found */ + @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier) throws NameNotFoundException { - return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName); + try { + return mService.isRestricted(packageName, settingIdentifier, + mContext.getUser().getIdentifier()); + } catch (IllegalArgumentException e) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -260,11 +263,15 @@ public final class EnhancedConfirmationManager { * @param packageName package name of the application to remove protection from * @throws NameNotFoundException if the provided package was not found */ + @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void clearRestriction(@NonNull String packageName) throws NameNotFoundException { - if (!isClearRestrictionAllowed(packageName)) { - throw new IllegalStateException("Clear restriction attempted but not allowed"); + try { + mService.clearRestriction(packageName, mContext.getUser().getIdentifier()); + } catch (IllegalArgumentException e) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } - setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED); } /** @@ -279,10 +286,17 @@ public final class EnhancedConfirmationManager { * restrictions from the provided app * @throws NameNotFoundException if the provided package was not found */ + @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isClearRestrictionAllowed(@NonNull String packageName) throws NameNotFoundException { - int state = getAppEcmState(packageName); - return state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED; + try { + return mService.isClearRestrictionAllowed(packageName, + mContext.getUser().getIdentifier()); + } catch (IllegalArgumentException e) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -293,13 +307,17 @@ public final class EnhancedConfirmationManager { * * @param packageName package name of the application which should be considered acknowledged * @throws NameNotFoundException if the provided package was not found - * * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void setClearRestrictionAllowed(@NonNull String packageName) throws NameNotFoundException { - if (isPackageEcmGuarded(packageName)) { - setAppEcmState(packageName, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED); + try { + mService.setClearRestrictionAllowed(packageName, mContext.getUser().getIdentifier()); + } catch (IllegalArgumentException e) { + throw new NameNotFoundException(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } @@ -321,92 +339,8 @@ public final class EnhancedConfirmationManager { return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE); } - private boolean isPackageEcmGuarded(@NonNull String packageName) throws NameNotFoundException { - // If this is a trusted installer or a pre-installed app, it is not ECM guarded - if (isAppTrustedInstaller(packageName) || isPackagePreinstalled(packageName)) { - return false; - } - - // If the package already has an explicitly-set state, use that - @EcmState int ecmState = getAppEcmState(packageName); - if (ecmState == EcmState.ECM_STATE_GUARDED - || ecmState == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED) { - return true; - } - if (ecmState == EcmState.ECM_STATE_NOT_GUARDED) { - return false; - } - - // Otherwise, lazily decide whether the app is considered guarded. - InstallSourceInfo installSource; - try { - installSource = mPackageManager.getInstallSourceInfo(packageName); - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "Package not found: " + packageName); - return false; - } - - // These install sources are always considered dangerous. - // PackageInstallers that are trusted can use these as a signal that the - // packages they've installed aren't as trusted as themselves. - int packageSource = installSource.getPackageSource(); - if (packageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE - || packageSource == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { - return true; - } - - // ECM doesn't consider a transitive chain of trust for install sources. - // If this package hasn't been explicitly handled by this point - // then it is exempt from ECM if the immediate parent is a trusted installer - boolean installedFromTrustedInstaller = installSource.getInstallingPackageName() != null - && isAppTrustedInstaller(installSource.getInstallingPackageName()); - return !installedFromTrustedInstaller; - } - - /** - * A "trusted installer" is any app with the INSTALL_PACKAGES permission. - */ - private boolean isAppTrustedInstaller(@NonNull String packageName) - throws NameNotFoundException { - int uid = getPackageUid(packageName); - // TODO(b/310654834): Support allow-list for OEM installer exemptions - return mContext.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, 0, uid) - == PackageManager.PERMISSION_GRANTED; - } - - private boolean isPackagePreinstalled(@NonNull String packageName) { - ApplicationInfo applicationInfo; - try { - applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, 0, mUser); - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "Package not found: " + packageName); - return false; - } - return ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - } - - private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState) - throws NameNotFoundException { - mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, - getPackageUid(packageName), packageName, ecmState); - } - - private @EcmState int getAppEcmState(@NonNull String packageName) throws NameNotFoundException { - return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, - getPackageUid(packageName), packageName, mAttributionTag, /* message */ - null); - } - - private boolean isSettingEcmProtected(@NonNull String settingIdentifier) { - if (PROTECTED_SETTINGS.contains(settingIdentifier)) { - return true; - } - // TODO(b/310654818): If this is a permission, coerce it into a PermissionGroup. - // TODO(b/310218979): Add role selections as protected settings - return false; - } - private int getPackageUid(String packageName) throws NameNotFoundException { - return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, mUser).uid; + return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, + mContext.getUser()).uid; } } diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl new file mode 100644 index 000000000..5149daa49 --- /dev/null +++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl @@ -0,0 +1,33 @@ +/* + * 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 android.app.ecm; + +import android.os.RemoteCallback; + +/** + * @hide + */ +interface IEnhancedConfirmationManager { + + boolean isRestricted(in String packageName, in String settingIdentifier, int userId); + + void clearRestriction(in String packageName, int userId); + + boolean isClearRestrictionAllowed(in String packageName, int userId); + + void setClearRestrictionAllowed(in String packageName, int userId); +} diff --git a/service/Android.bp b/service/Android.bp index 345ff4695..6850d26bd 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -135,6 +135,7 @@ java_sdk_library { installable: true, permitted_packages: [ "com.android.access", + "com.android.ecm", "com.android.permission", "com.android.role", "com.android.safetycenter", diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt index a3fd75930..495388afe 100644 --- a/service/jarjar-rules.txt +++ b/service/jarjar-rules.txt @@ -1,5 +1,6 @@ rule android.os.HandlerExecutor com.android.permission.jarjar.@0 -rule android.permission.flags.** com.android.permission.jarjar.@0 +rule android.permission.flags.*Flags* com.android.permission.jarjar.@0 +rule android.permission.flags.Flags com.android.permission.jarjar.@0 rule android.util.IndentingPrintWriter com.android.permission.jarjar.@0 rule com.android.internal.** com.android.permission.jarjar.@0 rule com.android.modules.** com.android.permission.jarjar.@0 diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java new file mode 100644 index 000000000..5b8513dc3 --- /dev/null +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -0,0 +1,307 @@ +/* + * 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.IntDef; +import android.annotation.UserIdInt; +import android.app.AppOpsManager; +import android.app.ecm.EnhancedConfirmationManager; +import android.app.ecm.IEnhancedConfirmationManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.InstallSourceInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Binder; +import android.os.Build; +import android.os.UserHandle; +import android.permission.flags.Flags; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.android.internal.util.Preconditions; +import com.android.permission.util.UserUtils; +import com.android.server.SystemService; + +import java.lang.annotation.Retention; + + +/** + * Service for ECM (Enhanced Confirmation Mode). + * + * @see EnhancedConfirmationManager + * + * @hide + */ +@Keep +@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) +@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +public class EnhancedConfirmationService extends SystemService { + private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName(); + + public EnhancedConfirmationService(@NonNull Context context) { + super(context); + } + + @Override + public void onStart() { + publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub()); + } + + private class Stub extends IEnhancedConfirmationManager.Stub { + + /** A map of ECM states to their corresponding app op states */ + @Retention(java.lang.annotation.RetentionPolicy.SOURCE) + @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED, + EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED, + EcmState.ECM_STATE_IMPLICIT}) + private @interface EcmState { + int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED; + int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED; + int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED; + int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT; + } + + private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>(); + + static { + PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); + // TODO(b/310654015): Add other explicitly protected settings + } + + private final @NonNull Context mContext; + private final String mAttributionTag; + private final AppOpsManager mAppOpsManager; + private final PackageManager mPackageManager; + + Stub() { + Context context = getContext(); + mContext = context; + mAttributionTag = context.getAttributionTag(); + mAppOpsManager = context.getSystemService(AppOpsManager.class); + mPackageManager = context.getPackageManager(); + } + + public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier, + @UserIdInt int userId) { + enforcePermissions("isRestricted", userId); + if (!UserUtils.isUserExistent(userId, getContext())) { + Log.e(LOG_TAG, "user " + userId + " does not exist"); + return false; + } + + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + Preconditions.checkStringNotEmpty(settingIdentifier, + "settingIdentifier cannot be null or empty"); + + try { + return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName, + userId); + } catch (NameNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + public void clearRestriction(@NonNull String packageName, @UserIdInt int userId) { + enforcePermissions("clearRestriction", userId); + if (!UserUtils.isUserExistent(userId, getContext())) { + return; + } + + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + + try { + int state = getAppEcmState(packageName, userId); + boolean isAllowed = state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED; + if (!isAllowed) { + throw new IllegalStateException("Clear restriction attempted but not allowed"); + } + setAppEcmState(packageName, EcmState.ECM_STATE_NOT_GUARDED, userId); + } catch (NameNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + public boolean isClearRestrictionAllowed(@NonNull String packageName, + @UserIdInt int userId) { + enforcePermissions("isClearRestrictionAllowed", userId); + if (!UserUtils.isUserExistent(userId, getContext())) { + return false; + } + + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + + try { + int state = getAppEcmState(packageName, userId); + return state == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED; + } catch (NameNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + public void setClearRestrictionAllowed(@NonNull String packageName, @UserIdInt int userId) { + enforcePermissions("setClearRestrictionAllowed", userId); + if (!UserUtils.isUserExistent(userId, getContext())) { + return; + } + + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + + try { + if (isPackageEcmGuarded(packageName, userId)) { + setAppEcmState(packageName, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED, + userId); + } + } catch (NameNotFoundException e) { + throw new IllegalArgumentException(e); + } + } + + private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) { + UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext); + // TODO(b/320512579): Enforce MANAGE_ENHANCED_CONFIRMATION_STATES instead + // + // Regarding permission enforcement: + // + // - Before implementing EnhancedConfirmationService, EnhancedConfirmationManager + // enforced MANAGE_APPOPS, UPDATE_APP_OPS_STATS, and MANAGE_APP_OPS_MODES. + // - We could enforce all three, but MANAGE_APPOPS should be enough: it + // is hidden API and is only granted to Shell and Settings, so the other two + // permissions are redundant. + // - We need to reference MANAGE_APPOPS by string here, because the current class + // is in a mainline module, and so does not have access to hidden API, and thus + // can't reference android.Manifest.permission.MANAGE_APPOPS. + // - In a follow-up CL, we plan to enforce a new permission anyway. But, doing + // that impacts calling apps, and also involves updating API code (RequiresPermission + // annotations), so that will go smoother if we do it in a separate CL. + mContext.enforceCallingPermission("android.permission.MANAGE_APPOPS", methodName); + } + + private boolean isPackageEcmGuarded(@NonNull String packageName, @UserIdInt int userId) + throws NameNotFoundException { + // If this is a trusted installer or a pre-installed app, it is not ECM guarded + if (isAppTrustedInstaller(packageName, userId) || isPackagePreinstalled(packageName, + userId)) { + return false; + } + + // If the package already has an explicitly-set state, use that + @EcmState int ecmState = getAppEcmState(packageName, userId); + if (ecmState == EcmState.ECM_STATE_GUARDED + || ecmState == EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED) { + return true; + } + if (ecmState == EcmState.ECM_STATE_NOT_GUARDED) { + return false; + } + + // Otherwise, lazily decide whether the app is considered guarded. + InstallSourceInfo installSource; + try { + installSource = mPackageManager.getInstallSourceInfo(packageName); + } catch (NameNotFoundException e) { + Log.w(LOG_TAG, "Package not found: " + packageName); + return false; + } + + // These install sources are always considered dangerous. + // PackageInstallers that are trusted can use these as a signal that the + // packages they've installed aren't as trusted as themselves. + int packageSource = installSource.getPackageSource(); + if (packageSource == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE + || packageSource == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) { + return true; + } + + // ECM doesn't consider a transitive chain of trust for install sources. + // If this package hasn't been explicitly handled by this point + // then it is exempt from ECM if the immediate parent is a trusted installer + boolean installedFromTrustedInstaller = + installSource.getInstallingPackageName() != null && isAppTrustedInstaller( + installSource.getInstallingPackageName(), userId); + return !installedFromTrustedInstaller; + } + + /** + * A "trusted installer" is any app with the INSTALL_PACKAGES permission. + */ + private boolean isAppTrustedInstaller(@NonNull String packageName, @UserIdInt int userId) + throws NameNotFoundException { + int uid = getPackageUid(packageName, userId); + // TODO(b/310654834): Support allow-list for OEM installer exemptions + return mContext.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, 0, uid) + == PackageManager.PERMISSION_GRANTED; + } + + private boolean isPackagePreinstalled(@NonNull String packageName, @UserIdInt int userId) { + ApplicationInfo applicationInfo; + try { + applicationInfo = mPackageManager.getApplicationInfoAsUser(packageName, 0, + UserHandle.of(userId)); + } catch (NameNotFoundException e) { + Log.w(LOG_TAG, "Package not found: " + packageName); + return false; + } + return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } + + private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState, + @UserIdInt int userId) throws NameNotFoundException { + int packageUid = getPackageUid(packageName, userId); + final long identityToken = Binder.clearCallingIdentity(); + try { + mAppOpsManager.setMode(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, packageUid, + packageName, ecmState); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + private @EcmState int getAppEcmState(@NonNull String packageName, @UserIdInt int userId) + throws NameNotFoundException { + int packageUid = getPackageUid(packageName, userId); + final long identityToken = Binder.clearCallingIdentity(); + try { + return mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, + packageUid, packageName, mAttributionTag, /* message */ null); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + private boolean isSettingEcmProtected(@NonNull String settingIdentifier) { + if (PROTECTED_SETTINGS.contains(settingIdentifier)) { + return true; + } + // TODO(b/310654818): If this is a permission, coerce it into a PermissionGroup. + // TODO(b/310218979): Add role selections as protected settings + return false; + } + + private int getPackageUid(@NonNull String packageName, @UserIdInt int userId) + throws NameNotFoundException { + return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, + UserHandle.of(userId)).uid; + } + } +} diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml index fa0e59752..bd44fe6be 100644 --- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml @@ -2881,7 +2881,7 @@ <p>Protection level: signature @SystemApi @hide - @FlaggedApi("com.android.internal.telephony.flags.ap_domain_selection_enabled") + @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") --> <permission android:name="android.permission.BIND_DOMAIN_SELECTION_SERVICE" android:protectionLevel="signature" /> @@ -3667,6 +3667,13 @@ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL" android:protectionLevel="internal|role" /> + <!-- Allows an application to access EnhancedConfirmationManager. + @SystemApi + @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") + @hide This is not a third-party API (intended for OEMs and system apps). --> + <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.--> <permission android:name="android.permission.PROVISION_DEMO_DEVICE" android:protectionLevel="signature|setup|knownSigner" @@ -3889,6 +3896,14 @@ android:description="@string/permdesc_useDataInBackground" android:protectionLevel="normal" /> + <!-- Allows an application to subscribe to notifications about the nearby devices' presence + status change base on the UUIDs. + <p>Not for use by third-party applications.</p> + @FlaggedApi("android.companion.flags.device_uuid_presence") + --> + <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" + android:protectionLevel="signature|privileged" /> + <!-- Allows app to request to be associated with a device via {@link android.companion.CompanionDeviceManager} as a "watch" @@ -7569,6 +7584,16 @@ <permission android:name="android.permission.RUN_USER_INITIATED_JOBS" android:protectionLevel="normal"/> + <!-- @FlaggedApi("android.app.job.backup_jobs_exemption") + Gives applications whose <b>primary use case</b> is to backup or sync content increased + job execution allowance in order to complete the related work. The jobs must have a valid + content URI trigger and network constraint set. + <p>This is a special access permission that can be revoked by the system or the user. + <p>Protection level: signature|privileged|appop + --> + <permission android:name="android.permission.RUN_BACKUP_JOBS" + android:protectionLevel="signature|privileged|appop"/> + <!-- Allows a browser to invoke the set of credential candidate query apis. <p>Protection level: normal --> @@ -7768,11 +7793,11 @@ <!-- @SystemApi Allows an application to read the system grammatical gender. @FlaggedApi("android.app.system_terms_of_address_enabled") - <p>Protection level: signature|privileged|appop + <p>Protection level: signature|privileged @hide --> <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER" - android:protectionLevel="signature|privileged|appop"/> + android:protectionLevel="signature|privileged"/> <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> |