Loading flags/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -42,5 +42,6 @@ aconfig_declarations { "telecom_remote_connection_service.aconfig", "telecom_profile_user_flags.aconfig", "telecom_bluetoothdevicemanager_flags.aconfig", "telecom_non_critical_security_flags.aconfig", ], } flags/telecom_non_critical_security_flags.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.server.telecom.flags" container: "system" # OWNER=tjstuart TARGET=24Q4 flag { name: "unregister_unresolvable_accounts" namespace: "telecom" description: "When set, Telecom will unregister accounts if the service is not resolvable" bug: "281061708" } No newline at end of file src/com/android/server/telecom/PhoneAccountRegistrar.java +91 −7 Original line number Diff line number Diff line Loading @@ -981,6 +981,9 @@ public class PhoneAccountRegistrar { } enforceCharacterLimit(account); enforceIconSizeLimit(account); if (mTelecomFeatureFlags.unregisterUnresolvableAccounts()) { enforcePhoneAccountTargetService(account); } enforceMaxPhoneAccountLimit(account); if (mTelephonyFeatureFlags.simultaneousCallingIndications()) { enforceSimultaneousCallingRestrictionLimit(account); Loading @@ -988,6 +991,25 @@ public class PhoneAccountRegistrar { addOrReplacePhoneAccount(account); } /** * This method ensures that {@link PhoneAccount}s that have the {@link * PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} capability are not * backed by a {@link ConnectionService} * * @param account enforce the check on */ private void enforcePhoneAccountTargetService(PhoneAccount account) { if (phoneAccountRequiresBindPermission(account.getAccountHandle()) && hasTransactionalCallCapabilities(account)) { throw new IllegalArgumentException( "Error, the PhoneAccount you are registering has" + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS and the" + " PhoneAccountHandle's ComponentName#ClassName points to a" + " ConnectionService class. Either remove the capability or use a" + " different ClassName in the PhoneAccountHandle."); } } /** * Enforce an upper bound on the number of PhoneAccount's a package can register. * Most apps should only require 1-2. * Include disabled accounts. Loading @@ -996,13 +1018,17 @@ public class PhoneAccountRegistrar { * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached */ private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) { final PhoneAccountHandle accountHandle = account.getAccountHandle(); final UserHandle user = accountHandle.getUserHandle(); final ComponentName componentName = accountHandle.getComponentName(); if (getPhoneAccountHandles(0, null, componentName.getPackageName(), true /* includeDisabled */, user, false /* crossUserAccess */).size() >= MAX_PHONE_ACCOUNT_REGISTRATIONS) { int numOfAcctsRegisteredForPackage = mTelecomFeatureFlags.unregisterUnresolvableAccounts() ? cleanupAndGetVerifiedAccounts(account).size() : getPhoneAccountHandles( 0/* capabilities */, null /* uriScheme */, account.getAccountHandle().getComponentName().getPackageName(), true /* includeDisabled */, account.getAccountHandle().getUserHandle(), false /* crossUserAccess */).size(); // enforce the max phone account limit for the application registering accounts if (numOfAcctsRegisteredForPackage >= MAX_PHONE_ACCOUNT_REGISTRATIONS) { EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(), "enforceMaxPhoneAccountLimit"); throw new IllegalArgumentException( Loading @@ -1012,6 +1038,64 @@ public class PhoneAccountRegistrar { } } @VisibleForTesting public List<PhoneAccount> getRegisteredAccountsForPackageName(String packageName, UserHandle userHandle) { if (packageName == null) { return new ArrayList<>(); } List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size()); for (PhoneAccount m : mState.accounts) { PhoneAccountHandle handle = m.getAccountHandle(); if (!packageName.equals(handle.getComponentName().getPackageName())) { // Not the right package name; skip this one. continue; } // Do not count accounts registered under different users on the device. Otherwise, an // application can only have MAX_PHONE_ACCOUNT_REGISTRATIONS across all users. If the // DUT has multiple users, they should each get to register 10 accounts. Also, 3rd // party applications cannot create new UserHandles without highly privileged // permissions. if (!isVisibleForUser(m, userHandle, false)) { // Account is not visible for the current user; skip this one. continue; } accounts.add(m); } return accounts; } /** * Unregister {@link ConnectionService} accounts that no longer have a resolvable Service. This * means the Service has been disabled or died. Skip the verification for transactional * accounts. * * @param newAccount being registered * @return all the verified accounts. These accounts are now guaranteed to be backed by a * {@link ConnectionService} or do not need one (transactional accounts). */ @VisibleForTesting public List<PhoneAccount> cleanupAndGetVerifiedAccounts(PhoneAccount newAccount) { ArrayList<PhoneAccount> verifiedAccounts = new ArrayList<>(); List<PhoneAccount> unverifiedAccounts = getRegisteredAccountsForPackageName( newAccount.getAccountHandle().getComponentName().getPackageName(), newAccount.getAccountHandle().getUserHandle()); for (PhoneAccount account : unverifiedAccounts) { PhoneAccountHandle handle = account.getAccountHandle(); if (/* skip for transactional accounts since they don't require a ConnectionService */ !hasTransactionalCallCapabilities(account) && /* check if the {@link ConnectionService} has been disabled or can longer be found */ resolveComponent(handle).isEmpty()) { Log.i(this, " cAGVA: Cannot resolve the ConnectionService for" + " handle=[%s]; unregistering account", handle); unregisterPhoneAccount(handle); } else { verifiedAccounts.add(account); } } return verifiedAccounts; } /** * determine if there will be an issue writing the icon to memory * Loading tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -141,6 +141,7 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy, mTelephonyFeatureFlags, mFeatureFlags); when(mFeatureFlags.onlyUpdateTelephonyOnValidSubIds()).thenReturn(false); when(mFeatureFlags.unregisterUnresolvableAccounts()).thenReturn(true); when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false); } Loading Loading @@ -467,6 +468,60 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { PhoneAccount.SCHEME_TEL)); } /** * Verify when a {@link android.telecom.ConnectionService} is disabled or cannot be resolved, * all phone accounts are unregistered when calling * {@link PhoneAccountRegistrar#cleanupAndGetVerifiedAccounts(PhoneAccount)}. */ @Test public void testCannotResolveServiceUnregistersAccounts() throws Exception { ComponentName componentName = makeQuickConnectionServiceComponentName(); PhoneAccount account = makeQuickAccountBuilder("0", 0, USER_HANDLE_10) .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER | PhoneAccount.CAPABILITY_CALL_PROVIDER).build(); // add the ConnectionService and register a single phone account for it mComponentContextFixture.addConnectionService(componentName, Mockito.mock(IConnectionService.class)); registerAndEnableAccount(account); // verify the start state assertEquals(1, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); // remove the ConnectionService so that the account cannot be resolved anymore mComponentContextFixture.removeConnectionService(componentName, Mockito.mock(IConnectionService.class)); // verify the account is unregistered when fetching the phone accounts for the package assertEquals(1, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); assertEquals(0, mRegistrar.cleanupAndGetVerifiedAccounts(account).size()); assertEquals(0, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); } /** * Verify that if a client adds both the {@link * PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} capability AND is backed by a * {@link android.telecom.ConnectionService}, a {@link IllegalArgumentException} is thrown. */ @Test public void testConnectionServiceAndTransactionalAccount() throws Exception { PhoneAccount account = makeQuickAccountBuilder("0", 0, USER_HANDLE_10) .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build(); mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); try { registerAndEnableAccount(account); fail("failed to throw IllegalArgumentException"); } catch (IllegalArgumentException e) { // test passed, ignore Exception. } } @MediumTest @Test public void testSimCallManager() throws Exception { Loading Loading
flags/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -42,5 +42,6 @@ aconfig_declarations { "telecom_remote_connection_service.aconfig", "telecom_profile_user_flags.aconfig", "telecom_bluetoothdevicemanager_flags.aconfig", "telecom_non_critical_security_flags.aconfig", ], }
flags/telecom_non_critical_security_flags.aconfig 0 → 100644 +10 −0 Original line number Diff line number Diff line package: "com.android.server.telecom.flags" container: "system" # OWNER=tjstuart TARGET=24Q4 flag { name: "unregister_unresolvable_accounts" namespace: "telecom" description: "When set, Telecom will unregister accounts if the service is not resolvable" bug: "281061708" } No newline at end of file
src/com/android/server/telecom/PhoneAccountRegistrar.java +91 −7 Original line number Diff line number Diff line Loading @@ -981,6 +981,9 @@ public class PhoneAccountRegistrar { } enforceCharacterLimit(account); enforceIconSizeLimit(account); if (mTelecomFeatureFlags.unregisterUnresolvableAccounts()) { enforcePhoneAccountTargetService(account); } enforceMaxPhoneAccountLimit(account); if (mTelephonyFeatureFlags.simultaneousCallingIndications()) { enforceSimultaneousCallingRestrictionLimit(account); Loading @@ -988,6 +991,25 @@ public class PhoneAccountRegistrar { addOrReplacePhoneAccount(account); } /** * This method ensures that {@link PhoneAccount}s that have the {@link * PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} capability are not * backed by a {@link ConnectionService} * * @param account enforce the check on */ private void enforcePhoneAccountTargetService(PhoneAccount account) { if (phoneAccountRequiresBindPermission(account.getAccountHandle()) && hasTransactionalCallCapabilities(account)) { throw new IllegalArgumentException( "Error, the PhoneAccount you are registering has" + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS and the" + " PhoneAccountHandle's ComponentName#ClassName points to a" + " ConnectionService class. Either remove the capability or use a" + " different ClassName in the PhoneAccountHandle."); } } /** * Enforce an upper bound on the number of PhoneAccount's a package can register. * Most apps should only require 1-2. * Include disabled accounts. Loading @@ -996,13 +1018,17 @@ public class PhoneAccountRegistrar { * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached */ private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) { final PhoneAccountHandle accountHandle = account.getAccountHandle(); final UserHandle user = accountHandle.getUserHandle(); final ComponentName componentName = accountHandle.getComponentName(); if (getPhoneAccountHandles(0, null, componentName.getPackageName(), true /* includeDisabled */, user, false /* crossUserAccess */).size() >= MAX_PHONE_ACCOUNT_REGISTRATIONS) { int numOfAcctsRegisteredForPackage = mTelecomFeatureFlags.unregisterUnresolvableAccounts() ? cleanupAndGetVerifiedAccounts(account).size() : getPhoneAccountHandles( 0/* capabilities */, null /* uriScheme */, account.getAccountHandle().getComponentName().getPackageName(), true /* includeDisabled */, account.getAccountHandle().getUserHandle(), false /* crossUserAccess */).size(); // enforce the max phone account limit for the application registering accounts if (numOfAcctsRegisteredForPackage >= MAX_PHONE_ACCOUNT_REGISTRATIONS) { EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(), "enforceMaxPhoneAccountLimit"); throw new IllegalArgumentException( Loading @@ -1012,6 +1038,64 @@ public class PhoneAccountRegistrar { } } @VisibleForTesting public List<PhoneAccount> getRegisteredAccountsForPackageName(String packageName, UserHandle userHandle) { if (packageName == null) { return new ArrayList<>(); } List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size()); for (PhoneAccount m : mState.accounts) { PhoneAccountHandle handle = m.getAccountHandle(); if (!packageName.equals(handle.getComponentName().getPackageName())) { // Not the right package name; skip this one. continue; } // Do not count accounts registered under different users on the device. Otherwise, an // application can only have MAX_PHONE_ACCOUNT_REGISTRATIONS across all users. If the // DUT has multiple users, they should each get to register 10 accounts. Also, 3rd // party applications cannot create new UserHandles without highly privileged // permissions. if (!isVisibleForUser(m, userHandle, false)) { // Account is not visible for the current user; skip this one. continue; } accounts.add(m); } return accounts; } /** * Unregister {@link ConnectionService} accounts that no longer have a resolvable Service. This * means the Service has been disabled or died. Skip the verification for transactional * accounts. * * @param newAccount being registered * @return all the verified accounts. These accounts are now guaranteed to be backed by a * {@link ConnectionService} or do not need one (transactional accounts). */ @VisibleForTesting public List<PhoneAccount> cleanupAndGetVerifiedAccounts(PhoneAccount newAccount) { ArrayList<PhoneAccount> verifiedAccounts = new ArrayList<>(); List<PhoneAccount> unverifiedAccounts = getRegisteredAccountsForPackageName( newAccount.getAccountHandle().getComponentName().getPackageName(), newAccount.getAccountHandle().getUserHandle()); for (PhoneAccount account : unverifiedAccounts) { PhoneAccountHandle handle = account.getAccountHandle(); if (/* skip for transactional accounts since they don't require a ConnectionService */ !hasTransactionalCallCapabilities(account) && /* check if the {@link ConnectionService} has been disabled or can longer be found */ resolveComponent(handle).isEmpty()) { Log.i(this, " cAGVA: Cannot resolve the ConnectionService for" + " handle=[%s]; unregistering account", handle); unregisterPhoneAccount(handle); } else { verifiedAccounts.add(account); } } return verifiedAccounts; } /** * determine if there will be an issue writing the icon to memory * Loading
tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -141,6 +141,7 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy, mTelephonyFeatureFlags, mFeatureFlags); when(mFeatureFlags.onlyUpdateTelephonyOnValidSubIds()).thenReturn(false); when(mFeatureFlags.unregisterUnresolvableAccounts()).thenReturn(true); when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false); } Loading Loading @@ -467,6 +468,60 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase { PhoneAccount.SCHEME_TEL)); } /** * Verify when a {@link android.telecom.ConnectionService} is disabled or cannot be resolved, * all phone accounts are unregistered when calling * {@link PhoneAccountRegistrar#cleanupAndGetVerifiedAccounts(PhoneAccount)}. */ @Test public void testCannotResolveServiceUnregistersAccounts() throws Exception { ComponentName componentName = makeQuickConnectionServiceComponentName(); PhoneAccount account = makeQuickAccountBuilder("0", 0, USER_HANDLE_10) .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER | PhoneAccount.CAPABILITY_CALL_PROVIDER).build(); // add the ConnectionService and register a single phone account for it mComponentContextFixture.addConnectionService(componentName, Mockito.mock(IConnectionService.class)); registerAndEnableAccount(account); // verify the start state assertEquals(1, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); // remove the ConnectionService so that the account cannot be resolved anymore mComponentContextFixture.removeConnectionService(componentName, Mockito.mock(IConnectionService.class)); // verify the account is unregistered when fetching the phone accounts for the package assertEquals(1, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); assertEquals(0, mRegistrar.cleanupAndGetVerifiedAccounts(account).size()); assertEquals(0, mRegistrar.getRegisteredAccountsForPackageName(componentName.getPackageName(), USER_HANDLE_10).size()); } /** * Verify that if a client adds both the {@link * PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} capability AND is backed by a * {@link android.telecom.ConnectionService}, a {@link IllegalArgumentException} is thrown. */ @Test public void testConnectionServiceAndTransactionalAccount() throws Exception { PhoneAccount account = makeQuickAccountBuilder("0", 0, USER_HANDLE_10) .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED | PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build(); mComponentContextFixture.addConnectionService( makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class)); try { registerAndEnableAccount(account); fail("failed to throw IllegalArgumentException"); } catch (IllegalArgumentException e) { // test passed, ignore Exception. } } @MediumTest @Test public void testSimCallManager() throws Exception { Loading