diff options
author | 2024-12-03 15:48:29 -0800 | |
---|---|---|
committer | 2024-12-06 23:33:51 +0000 | |
commit | 33dc5c7223185b27dba4402750b1b78f1f42a246 (patch) | |
tree | 8acba6bbe674d749cd4ffbccdc4e0096fab64612 | |
parent | 3033e4837bb5958cf2d0ef8b8090867677570dbe (diff) |
Add in-call version of ECM dialog, remove new ECM api
This dialog looks similar to the existing ECM dialog, but has a
different message, about being blocked while in a phone call
Also removes the ecm "isUnknownCallOngoing", in favor of using
"isRestricted"
Test: atest EnhancedConfirmationInCallTest
Flag: android.permission.flags.unknown_call_package_install_blocking_enabled
Relnote: android 25Q2 feature
LOW_COVERAGE_REASON=FLAG_NOT_ENABLED
Change-Id: I113114c15df3df483e290f0bab00f5cecb2b44f8
7 files changed, 98 insertions, 98 deletions
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 9fa361b57..82c7889c6 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -2016,6 +2016,13 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo <!--Content for dialog displayed to tell user that settings are blocked by setting restrictions [CHAR LIMIT=NONE] --> <string name="enhanced_confirmation_dialog_desc">For your security, this setting is currently unavailable.</string> + <!--Title for dialog displayed to tell user that settings are blocked due to the phone state (such as being in a call with an unknown number) [CHAR LIMIT=50] --> + <string name="enhanced_confirmation_phone_state_dialog_title">Action not available while on a phone call</string> + <!--Content for dialog displayed to tell user that settings are blocked due to the phone state (such as being in a call with an unknown number) [CHAR LIMIT=NONE] --> + <string name="enhanced_confirmation_phone_state_dialog_desc">Allowing apps to install other apps is not allowed during a phone call.\n\n + Scammers often request this type of action during phone call conversations, so it\u2019s blocked to protect you. If you are being guided to take this action + by someone you don\u2019t know, it might be a scam.</string> + <!--Title for dialog displayed to tell user that permissions are blocked by setting restrictions [CHAR LIMIT=50] --> <string name="enhanced_confirmation_dialog_title_permission">App was denied access to <xliff:g id="permission_name" example="contacts">%1$s</xliff:g></string> <!--Content for dialog displayed to tell user that settings are blocked by setting restrictions [CHAR LIMIT=NONE] --> diff --git a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt index bc6f774ad..e6cf094e3 100644 --- a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt +++ b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt @@ -18,6 +18,7 @@ package com.android.permissioncontroller.ecm import android.annotation.SuppressLint import android.app.AlertDialog +import android.app.AppOpsManager import android.app.Dialog import android.app.ecm.EnhancedConfirmationManager import android.content.Context @@ -65,6 +66,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { finish() return } + if (savedInstanceState != null) { wasClearRestrictionAllowed = savedInstanceState.getBoolean(KEY_WAS_CLEAR_RESTRICTION_ALLOWED) @@ -79,11 +81,19 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { require(uid != Process.INVALID_UID) { "EXTRA_UID cannot be null or invalid" } require(!packageName.isNullOrEmpty()) { "EXTRA_PACKAGE_NAME cannot be null or empty" } require(!settingIdentifier.isNullOrEmpty()) { "EXTRA_SUBJECT cannot be null or empty" } - wasClearRestrictionAllowed = setClearRestrictionAllowed(packageName, UserHandle.getUserHandleForUid(uid)) val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp) + if ( + SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp) == + SettingType.BLOCKED_DUE_TO_PHONE_STATE && + !Flags.unknownCallPackageInstallBlockingEnabled() + ) { + finish() + return + } + if (DeviceUtils.isWear(this)) { WearEnhancedConfirmationDialogFragment.newInstance(setting.title, setting.message) .show(supportFragmentManager, WearEnhancedConfirmationDialogFragment.TAG) @@ -116,7 +126,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { fun fromIdentifier( context: Context, settingIdentifier: String, - isEcmInApp: Boolean + isEcmInApp: Boolean, ): Setting { val settingType = SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp) val label = @@ -124,7 +134,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { SettingType.PLATFORM_PERMISSION -> KotlinUtils.getPermGroupLabel( context, - PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!! + PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!!, ) SettingType.PLATFORM_PERMISSION_GROUP -> KotlinUtils.getPermGroupLabel(context, settingIdentifier) @@ -132,15 +142,22 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { context.getString( Roles.get(context)[settingIdentifier]!!.shortLabelResource ) + SettingType.BLOCKED_DUE_TO_PHONE_STATE, SettingType.OTHER -> null } - val url = - context.getString(R.string.help_url_action_disabled_by_restricted_settings) - return Setting( - title = settingType.titleRes?.let { context.getString(it, label) }, + var title: String? + var message: CharSequence? + if (settingType == SettingType.BLOCKED_DUE_TO_PHONE_STATE) { + title = settingType.titleRes?.let { context.getString(it) } + message = settingType.messageRes?.let { context.getString(it) } + } else { + val url = + context.getString(R.string.help_url_action_disabled_by_restricted_settings) + title = (settingType.titleRes?.let { context.getString(it, label) }) message = settingType.messageRes?.let { Html.fromHtml(context.getString(it, url), 0) } - ) + } + return Setting(title, message) } } } @@ -148,29 +165,35 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { private enum class SettingType(val titleRes: Int?, val messageRes: Int?) { PLATFORM_PERMISSION( R.string.enhanced_confirmation_dialog_title_permission, - R.string.enhanced_confirmation_dialog_desc_permission + R.string.enhanced_confirmation_dialog_desc_permission, ), PLATFORM_PERMISSION_GROUP( R.string.enhanced_confirmation_dialog_title_permission, - R.string.enhanced_confirmation_dialog_desc_permission + R.string.enhanced_confirmation_dialog_desc_permission, ), ROLE( R.string.enhanced_confirmation_dialog_title_role, - R.string.enhanced_confirmation_dialog_desc_role + R.string.enhanced_confirmation_dialog_desc_role, ), OTHER( R.string.enhanced_confirmation_dialog_title_settings_default, - R.string.enhanced_confirmation_dialog_desc_settings_default + R.string.enhanced_confirmation_dialog_desc_settings_default, + ), + BLOCKED_DUE_TO_PHONE_STATE( + R.string.enhanced_confirmation_phone_state_dialog_title, + R.string.enhanced_confirmation_phone_state_dialog_desc, ); companion object { fun fromIdentifier( context: Context, settingIdentifier: String, - isEcmInApp: Boolean + isEcmInApp: Boolean, ): SettingType { - if (!isEcmInApp) return SettingType.OTHER return when { + settingIdentifier == AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES -> + BLOCKED_DUE_TO_PHONE_STATE + !isEcmInApp -> OTHER PermissionMapping.isRuntimePlatformPermission(settingIdentifier) && PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null -> PLATFORM_PERMISSION @@ -178,7 +201,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { PLATFORM_PERMISSION_GROUP settingIdentifier.startsWith("android.app.role.") && Roles.get(context).containsKey(settingIdentifier) -> ROLE - else -> SettingType.OTHER + else -> OTHER } } } @@ -188,7 +211,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { this.dialogResult = dialogResult setResult( RESULT_OK, - Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) } + Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) }, ) finish() } @@ -200,7 +223,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID), settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT)!!, firstShowForApp = !wasClearRestrictionAllowed, - dialogResult = dialogResult + dialogResult = dialogResult, ) } } @@ -249,7 +272,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { private fun createDialogView( context: Context, title: String?, - message: CharSequence? + message: CharSequence?, ): View = LayoutInflater.from(context) .inflate(R.layout.enhanced_confirmation_dialog, null) diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index efab125bd..fc06d6927 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -6,9 +6,7 @@ package android.app.ecm { method @NonNull public android.content.Intent createRestrictedSettingDialogIntent(@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; - method @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public boolean isUnknownCallOngoing(); method @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) public void setClearRestrictionAllowed(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException; - field @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public static final String ACTION_SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG"; field public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; } diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java index e497ba5f4..db05a0af6 100644 --- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java +++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java @@ -42,7 +42,7 @@ import java.lang.annotation.Retention; * This class provides the core API for ECM (Enhanced Confirmation Mode). ECM is a feature that * restricts access to protected **settings** (i.e., sensitive resources) by restricted **apps** * (apps from from dangerous sources, such as sideloaded packages or packages downloaded from a web - * browser). + * browser), or restricts settings globally based on device state. * * <p>Specifically, this class provides the ability to: * @@ -70,6 +70,9 @@ import java.lang.annotation.Retention; * particular app restricted is an implementation detail of ECM. However, the user is able to * clear any restricted app's restriction status (i.e, un-restrict it), after which ECM will * consider the app **not restricted**. + * <li>A setting may be globally restricted based on device state. In this case, any app may be + * automatically considered *restricted*, regardless of the app's restriction state. Users + * cannot un-restrict the app, in these cases. * </ol> * * Why is ECM needed? Consider the following (pre-ECM) scenario: @@ -199,15 +202,6 @@ public final class EnhancedConfirmationManager { public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; - /** - * Shows a dialog indicating a setting has been blocked due to the phone state (such as being - * on a call with an unknown number). Opened when a setting is blocked. - */ - @SdkConstant(BROADCAST_INTENT_ACTION) - @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) - public static final String ACTION_SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG = - "android.app.ecm.action.SHOW_ECM_PHONE_STATE_BLOCKED_SETTING_DIALOG"; - /** 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, @@ -321,6 +315,9 @@ public final class EnhancedConfirmationManager { * <p>This should be called from the "Restricted setting" dialog (which {@link * #createRestrictedSettingDialogIntent} directs to) upon being presented to the user. * + * <p>This restriction clearing does not apply to any settings that are restricted based on + * global device state + * * @param packageName package name of the application which should be considered acknowledged * @throws NameNotFoundException if the provided package was not found */ @@ -337,23 +334,6 @@ public final class EnhancedConfirmationManager { } /** - * Returns whether the enhanced confirmation system thinks a call with an unknown party is - * occurring - * - * @hide - */ - @SystemApi - @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED) - @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) - public boolean isUnknownCallOngoing() { - try { - return mService.isUntrustedCallOngoing(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Gets an intent that will open the "Restricted setting" dialog for the specified package * and setting. * diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl index 833485890..5149daa49 100644 --- a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl +++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl @@ -30,7 +30,4 @@ interface IEnhancedConfirmationManager { boolean isClearRestrictionAllowed(in String packageName, int userId); void setClearRestrictionAllowed(in String packageName, int userId); - - boolean isUntrustedCallOngoing(); - } diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java index dde9fe2fd..af2e81133 100644 --- a/service/java/com/android/ecm/EnhancedConfirmationService.java +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -239,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); @@ -259,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; @@ -287,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); } @@ -351,10 +367,11 @@ public class EnhancedConfirmationService extends SystemService { } } - @Override - public boolean isUntrustedCallOngoing() { - enforcePermissions("isUntrustedCallOngoing", - UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier()); + private boolean isUntrustedCallOngoing() { + if (!Flags.unknownCallPackageInstallBlockingEnabled()) { + return false; + } + if (hasCallOfType(CALL_TYPE_EMERGENCY)) { // If we have an emergency call, return false always. return false; @@ -496,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/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt index cbf4734d5..c410f9c9c 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt @@ -16,7 +16,7 @@ package android.permissionui.cts -import android.Manifest +import android.app.AppOpsManager import android.app.Instrumentation import android.app.ecm.EnhancedConfirmationManager import android.content.ContentProviderOperation @@ -37,7 +37,6 @@ import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity -import java.util.concurrent.Callable import org.junit.After import org.junit.AfterClass import org.junit.Assert @@ -152,47 +151,18 @@ class EnhancedConfirmationInCallTest { addedContacts.keys.forEach { removeContact(it) } } - private fun getInUnknownCallState(): Boolean { - return callWithShellPermissionIdentity { ecm.isUnknownCallOngoing } - } - - @Test - fun testCannotReadOngoingState_WithoutPermission() { - try { - ecm.isUnknownCallOngoing - Assert.fail() - } catch (expected: SecurityException) { - Assert.assertTrue( - expected.message?.contains( - Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES - ) == true - ) + private fun isSettingRestricted(): Boolean { + return callWithShellPermissionIdentity { + ecm.isRestricted(context.packageName, AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES) } - - val unexpectedException = - callWithShellPermissionIdentity( - Callable { - try { - ecm.isUnknownCallOngoing - null - } catch (unexpected: SecurityException) { - // Catching the exception, because exceptions thrown inside - // run/callWithShellPermissionIdentity are obscured by the rethrow - // from run/call. - unexpected - } - }, - Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, - ) - Assert.assertNull(unexpectedException) } @Test fun testIncomingCall_NonContact() { voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, NON_CONTACT_PHONE_NUMBER) - Assert.assertTrue(getInUnknownCallState()) + Assert.assertTrue(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -200,9 +170,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If no phone number is given, the display name will be checked voipService.createCallAndWaitForActive(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -210,9 +180,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If the phone number matches, the display name is not checked voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } @Test @@ -222,10 +192,10 @@ class EnhancedConfirmationInCallTest { voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) addContact(tempContactDisplay, tempContactPhone) // State should not be recomputed just because the contact is newly added - Assert.assertTrue(getInUnknownCallState()) + Assert.assertTrue(isSettingRestricted()) voipService.endCallAndWaitForInactive() voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) // A new call should recognize our contact, and mark the call as trusted - Assert.assertFalse(getInUnknownCallState()) + Assert.assertFalse(isSettingRestricted()) } } |