Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f69e7808 authored by Hui Wang's avatar Hui Wang
Browse files

Update phone account APIs for work profile split mode

Bug: 296067229
Test: atest TelecomUnitTests
Test: manual with test app

Change-Id: I848dc6c18b4241c8528817332b134247cf29804e
parent 95cb9d2c
Loading
Loading
Loading
Loading
+99 −9
Original line number Diff line number Diff line
@@ -181,16 +181,19 @@ public class PhoneAccountRegistrar {
    private interface PhoneAccountRegistrarWriteLock {}
    private final PhoneAccountRegistrarWriteLock mWriteLock =
            new PhoneAccountRegistrarWriteLock() {};
    private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;

    @VisibleForTesting
    public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock,
            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
        this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy);
            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
            com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {
        this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy, telephonyFeatureFlags);
    }

    @VisibleForTesting
    public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock, String fileName,
            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy,
            com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags) {

        mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));

@@ -204,6 +207,13 @@ public class PhoneAccountRegistrar {
        mAppLabelProxy = appLabelProxy;
        mCurrentUserHandle = Process.myUserHandle();

        if (telephonyFeatureFlags != null) {
            mTelephonyFeatureFlags = telephonyFeatureFlags;
        } else {
            mTelephonyFeatureFlags =
                    new com.android.internal.telephony.flags.FeatureFlagsImpl();
        }

        // register context based receiver to clean up orphan phone accounts
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
@@ -697,6 +707,24 @@ public class PhoneAccountRegistrar {
        }
    }

    private boolean isMatchedUser(PhoneAccount account, UserHandle userHandle) {
        if (account == null) {
            return false;
        }

        if (userHandle == null) {
            Log.w(this, "userHandle is null in isVisibleForUser");
            return false;
        }

        UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
        if (phoneAccountUserHandle == null) {
            return false;
        }

        return phoneAccountUserHandle.equals(userHandle);
    }

    private boolean isVisibleForUser(PhoneAccount account, UserHandle userHandle,
            boolean acrossProfiles) {
        if (account == null) {
@@ -763,11 +791,11 @@ public class PhoneAccountRegistrar {
     */
    public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle,
            boolean crossUserAccess) {
        return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess);
        return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess, true);
    }

    public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle, boolean crossUserAccess) {
        return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess);
        return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess, true);
    }

    /**
@@ -858,7 +886,7 @@ public class PhoneAccountRegistrar {
    public List<PhoneAccountHandle> getAllPhoneAccountHandlesForPackage(UserHandle userHandle,
            String packageName) {
        return getPhoneAccountHandles(0, null, packageName, true /* includeDisabled */, userHandle,
                true /* crossUserAccess */);
                true /* crossUserAccess */, true);
    }

    /**
@@ -1490,7 +1518,19 @@ public class PhoneAccountRegistrar {
            UserHandle userHandle,
            boolean crossUserAccess) {
        return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
                packageName, includeDisabledAccounts, userHandle, crossUserAccess);
                packageName, includeDisabledAccounts, userHandle, crossUserAccess, false);
    }

    private List<PhoneAccountHandle> getPhoneAccountHandles(
            int capabilities,
            String uriScheme,
            String packageName,
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess,
            boolean includeAll) {
        return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
                packageName, includeDisabledAccounts, userHandle, crossUserAccess, includeAll);
    }

    /**
@@ -1505,11 +1545,24 @@ public class PhoneAccountRegistrar {
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess) {
        return getPhoneAccountHandles(capabilities, excludedCapabilities, uriScheme, packageName,
                includeDisabledAccounts, userHandle, crossUserAccess, false);
    }

    private List<PhoneAccountHandle> getPhoneAccountHandles(
            int capabilities,
            int excludedCapabilities,
            String uriScheme,
            String packageName,
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess,
            boolean includeAll) {
        List<PhoneAccountHandle> handles = new ArrayList<>();

        for (PhoneAccount account : getPhoneAccounts(
                capabilities, excludedCapabilities, uriScheme, packageName,
                includeDisabledAccounts, userHandle, crossUserAccess)) {
                includeDisabledAccounts, userHandle, crossUserAccess, includeAll)) {
            handles.add(account.getAccountHandle());
        }
        return handles;
@@ -1523,7 +1576,19 @@ public class PhoneAccountRegistrar {
            UserHandle userHandle,
            boolean crossUserAccess) {
        return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
                includeDisabledAccounts, userHandle, crossUserAccess);
                includeDisabledAccounts, userHandle, crossUserAccess, false);
    }

    private List<PhoneAccount> getPhoneAccounts(
            int capabilities,
            String uriScheme,
            String packageName,
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess,
            boolean includeAll) {
        return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
                includeDisabledAccounts, userHandle, crossUserAccess, includeAll);
    }

    /**
@@ -1545,7 +1610,22 @@ public class PhoneAccountRegistrar {
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess) {
        return getPhoneAccounts(capabilities, excludedCapabilities, uriScheme, packageName,
                includeDisabledAccounts, userHandle, crossUserAccess, false);
    }

    @VisibleForTesting
    public List<PhoneAccount> getPhoneAccounts(
            int capabilities,
            int excludedCapabilities,
            String uriScheme,
            String packageName,
            boolean includeDisabledAccounts,
            UserHandle userHandle,
            boolean crossUserAccess,
            boolean includeAll) {
        List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
        List<PhoneAccount> matchedAccounts = new ArrayList<>(mState.accounts.size());
        for (PhoneAccount m : mState.accounts) {
            if (!(m.isEnabled() || includeDisabledAccounts)) {
                // Do not include disabled accounts.
@@ -1579,12 +1659,22 @@ public class PhoneAccountRegistrar {
                // Not the right package name; skip this one.
                continue;
            }
            if (isMatchedUser(m, userHandle)) {
                matchedAccounts.add(m);
            }
            if (!crossUserAccess && !isVisibleForUser(m, userHandle, false)) {
                // Account is not visible for the current user; skip this one.
                continue;
            }
            accounts.add(m);
        }

        // Return the account if it exactly matches. Otherwise, return any account that's visible
        if (mTelephonyFeatureFlags.workProfileApiSplit() && !crossUserAccess && !includeAll
                && !matchedAccounts.isEmpty()) {
            return matchedAccounts;
        }

        return accounts;
    }

+48 −3
Original line number Diff line number Diff line
@@ -356,9 +356,28 @@ public class TelecomServiceImpl {

        @Override
        public ParceledListSlice<PhoneAccountHandle> getCallCapablePhoneAccounts(
                boolean includeDisabledAccounts, String callingPackage, String callingFeatureId) {
                boolean includeDisabledAccounts, String callingPackage,
                String callingFeatureId, boolean acrossProfiles) {
            try {
                Log.startSession("TSI.gCCPA", Log.getPackageAbbreviation(callingPackage));

                if (mTelephonyFeatureFlags.workProfileApiSplit()) {
                    if (acrossProfiles) {
                        enforceInAppCrossProfilePermission();
                    }

                    if (includeDisabledAccounts && !canReadPrivilegedPhoneState(
                            callingPackage, "getCallCapablePhoneAccounts")) {
                        throw new SecurityException(
                                "Requires READ_PRIVILEGED_PHONE_STATE permission.");
                    }

                    if (!includeDisabledAccounts && !canReadPhoneState(callingPackage,
                            callingFeatureId, "Requires READ_PHONE_STATE permission.")) {
                        throw new SecurityException("Requires READ_PHONE_STATE permission.");
                    }
                }

                if (includeDisabledAccounts &&
                        !canReadPrivilegedPhoneState(
                                callingPackage, "getCallCapablePhoneAccounts")) {
@@ -370,7 +389,11 @@ public class TelecomServiceImpl {
                }
                synchronized (mLock) {
                    final UserHandle callingUserHandle = Binder.getCallingUserHandle();
                    boolean crossUserAccess = hasInAppCrossUserPermission();
                    boolean crossUserAccess = mTelephonyFeatureFlags.workProfileApiSplit()
                            && !acrossProfiles ? false
                            : (mTelephonyFeatureFlags.workProfileApiSplit()
                                    ? hasInAppCrossProfilePermission()
                                    : hasInAppCrossUserPermission());
                    long token = Binder.clearCallingIdentity();
                    try {
                        return new ParceledListSlice<>(
@@ -638,6 +661,7 @@ public class TelecomServiceImpl {
        public ParceledListSlice<PhoneAccountHandle> getAllPhoneAccountHandles() {
            try {
                Log.startSession("TSI.gAPAH");

                try {
                    enforceModifyPermission(
                            "getAllPhoneAccountHandles requires MODIFY_PHONE_STATE permission.");
@@ -656,7 +680,7 @@ public class TelecomServiceImpl {
                                .getAllPhoneAccountHandles(callingUserHandle,
                                        crossUserAccess));
                    } catch (Exception e) {
                        Log.e(this, e, "getAllPhoneAccounts");
                        Log.e(this, e, "getAllPhoneAccountsHandles");
                        throw e;
                    } finally {
                        Binder.restoreCallingIdentity(token);
@@ -2558,6 +2582,8 @@ public class TelecomServiceImpl {
    private TransactionManager mTransactionManager;
    private final TransactionalServiceRepository mTransactionalServiceRepository;
    private final FeatureFlags mFeatureFlags;
    private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;


    public TelecomServiceImpl(
            Context context,
@@ -2569,6 +2595,7 @@ public class TelecomServiceImpl {
            SubscriptionManagerAdapter subscriptionManagerAdapter,
            SettingsSecureAdapter settingsSecureAdapter,
            FeatureFlags featureFlags,
            com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
            TelecomSystem.SyncRoot lock) {
        mContext = context;
        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -2577,6 +2604,12 @@ public class TelecomServiceImpl {

        mCallsManager = callsManager;
        mFeatureFlags = featureFlags;
        if (telephonyFeatureFlags != null) {
            mTelephonyFeatureFlags = telephonyFeatureFlags;
        } else {
            mTelephonyFeatureFlags =
                    new com.android.internal.telephony.flags.FeatureFlagsImpl();
        }
        mLock = lock;
        mPhoneAccountRegistrar = phoneAccountRegistrar;
        mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
@@ -2947,12 +2980,24 @@ public class TelecomServiceImpl {
                        + " INTERACT_ACROSS_USERS permission");
    }

    private void enforceInAppCrossProfilePermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.INTERACT_ACROSS_PROFILES, "Must be system or have"
                        + " INTERACT_ACROSS_PROFILES permission");
    }

    private boolean hasInAppCrossUserPermission() {
        return mContext.checkCallingOrSelfPermission(
                Manifest.permission.INTERACT_ACROSS_USERS)
                == PackageManager.PERMISSION_GRANTED;
    }

    private boolean hasInAppCrossProfilePermission() {
        return mContext.checkCallingOrSelfPermission(
                Manifest.permission.INTERACT_ACROSS_PROFILES)
                == PackageManager.PERMISSION_GRANTED;
    }

    // to be used for TestApi methods that can only be called with SHELL UID.
    private void enforceShellOnly(int callingUid, String message) {
        if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
+2 −1
Original line number Diff line number Diff line
@@ -245,7 +245,7 @@ public class TelecomSystem {
        try {
            mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
                    packageName -> AppLabelProxy.Util.getAppLabel(
                            mContext.getPackageManager(), packageName));
                            mContext.getPackageManager(), packageName), null);

            mContactsAsyncHelper = contactsAsyncHelperFactory.create(
                    new ContactsAsyncHelper.ContentResolverAdapter() {
@@ -485,6 +485,7 @@ public class TelecomSystem {
                    new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
                    new TelecomServiceImpl.SettingsSecureAdapterImpl(),
                    featureFlags,
                    null,
                    mLock);
        } finally {
            Log.endSession();
+55 −2
Original line number Diff line number Diff line
@@ -117,6 +117,7 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase {
    @Mock private TelecomManager mTelecomManager;
    @Mock private DefaultDialerCache mDefaultDialerCache;
    @Mock private AppLabelProxy mAppLabelProxy;
    @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;

    @Override
    @Before
@@ -134,8 +135,10 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase {
        when(mAppLabelProxy.getAppLabel(anyString()))
                .thenReturn(TEST_LABEL);
        mRegistrar = new PhoneAccountRegistrar(
                mComponentContextFixture.getTestDouble().getApplicationContext(),
                mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
                mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME,
                mDefaultDialerCache, mAppLabelProxy, mTelephonyFeatureFlags);
        when(mFeatureFlags.onlyUpdateTelephonyOnValidSubIds()).thenReturn(false);
        when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
    }

    @Override
@@ -1734,6 +1737,56 @@ public class PhoneAccountRegistrarTest extends TelecomTestCase {
        }
    }

    @Test
    public void testGetPhoneAccountAcrossUsers() throws Exception {
        when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);
        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                Mockito.mock(IConnectionService.class));

        PhoneAccount accountForCurrent = makeQuickAccountBuilder("id_0", 0, UserHandle.CURRENT)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
        PhoneAccount accountForAll = makeQuickAccountBuilder("id_0", 0, UserHandle.ALL)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER
                        | PhoneAccount.CAPABILITY_MULTI_USER).build();
        PhoneAccount accountForWorkProfile = makeQuickAccountBuilder("id_1", 1, USER_HANDLE_10)
                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                        | PhoneAccount.CAPABILITY_CALL_PROVIDER).build();

        registerAndEnableAccount(accountForCurrent);
        registerAndEnableAccount(accountForAll);
        registerAndEnableAccount(accountForWorkProfile);

        List<PhoneAccount> accountsForUser = mRegistrar.getPhoneAccounts(0, 0,
                null, null, false, USER_HANDLE_10, false, false);
        List<PhoneAccount> accountsVisibleUser = mRegistrar.getPhoneAccounts(0, 0,
                null, null, false, USER_HANDLE_10, false, true);
        List<PhoneAccount> accountsAcrossUser = mRegistrar.getPhoneAccounts(0, 0,
                null, null, false, USER_HANDLE_10, true, false);

        // Return the account exactly matching the user if it exists
        assertEquals(1, accountsForUser.size());
        assertTrue(accountsForUser.contains(accountForWorkProfile));
        // The accounts visible to the user without across user permission
        assertEquals(2, accountsVisibleUser.size());
        assertTrue(accountsVisibleUser.containsAll(accountsForUser));
        assertTrue(accountsVisibleUser.contains(accountForAll));
        // The accounts visible to the user with across user permission
        assertEquals(3, accountsAcrossUser.size());
        assertTrue(accountsAcrossUser.containsAll(accountsVisibleUser));
        assertTrue(accountsAcrossUser.contains(accountForCurrent));

        mRegistrar.unregisterPhoneAccount(accountForWorkProfile.getAccountHandle());

        accountsForUser = mRegistrar.getPhoneAccounts(0, 0,
                null, null, false, USER_HANDLE_10, false, false);

        // Return the account visible for the user if no account exactly matches the user
        assertEquals(1, accountsForUser.size());
        assertTrue(accountsForUser.contains(accountForAll));
    }

    private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
        return new PhoneAccount.Builder(handle, TEST_LABEL)
                .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+60 −3
Original line number Diff line number Diff line
@@ -197,6 +197,7 @@ public class TelecomServiceImplTest extends TelecomTestCase {
    @Mock private TransactionManager mTransactionManager;
    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
    @Mock private FeatureFlags mFeatureFlags;
    @Mock private com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;

    @Mock private InCallController mInCallController;

@@ -251,6 +252,7 @@ public class TelecomServiceImplTest extends TelecomTestCase {
                mSubscriptionManagerAdapter,
                mSettingsSecureAdapter,
                mFeatureFlags,
                mTelephonyFeatureFlags,
                mLock);
        telecomServiceImpl.setTransactionManager(mTransactionManager);
        telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
@@ -270,6 +272,7 @@ public class TelecomServiceImplTest extends TelecomTestCase {
        mPackageManager = mContext.getPackageManager();
        when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
        when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(true);
        when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
    }

    @Override
@@ -535,10 +538,64 @@ public class TelecomServiceImplTest extends TelecomTestCase {

        assertEquals(fullPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null).getList());
                        true, DEFAULT_DIALER_PACKAGE, null, false).getList());
        assertEquals(smallPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        false, DEFAULT_DIALER_PACKAGE, null).getList());
                        false, DEFAULT_DIALER_PACKAGE, null, false).getList());
    }

    @SmallTest
    @Test
    public void testGetCallCapablePhoneAccountsAcrossProfiles() throws RemoteException {
        List<PhoneAccountHandle> fullPHList = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
        List<PhoneAccountHandle> smallPHList = List.of(SIP_PA_HANDLE_17);

        // Returns all accounts when getCallCapablePhoneAccounts is called with across user.
        doReturn(fullPHList).when(mFakePhoneAccountRegistrar).getCallCapablePhoneAccounts(
                nullable(String.class), anyBoolean(), nullable(UserHandle.class), eq(true));
        // Returns one account when getCallCapablePhoneAccounts is called without across user.
        doReturn(smallPHList).when(mFakePhoneAccountRegistrar).getCallCapablePhoneAccounts(
                nullable(String.class), anyBoolean(), nullable(UserHandle.class), eq(false));
        // With across user permission
        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
                eq(Manifest.permission.INTERACT_ACROSS_USERS));

        assertEquals(fullPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, false).getList());

        // Without across user permission
        doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
                eq(Manifest.permission.INTERACT_ACROSS_USERS));

        assertEquals(smallPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, false).getList());

        // Enabled the feature flag of the work profile split mode
        when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(true);

        // With across user permission
        doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
                eq(Manifest.permission.INTERACT_ACROSS_PROFILES));

        assertEquals(fullPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, true).getList());
        assertEquals(smallPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, false).getList());

        // Without across user permission
        doReturn(PackageManager.PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
                eq(Manifest.permission.INTERACT_ACROSS_PROFILES));

        assertEquals(smallPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, true).getList());
        assertEquals(smallPHList,
                mTSIBinder.getCallCapablePhoneAccounts(
                        true, DEFAULT_DIALER_PACKAGE, null, false).getList());
    }

    @SmallTest
@@ -550,7 +607,7 @@ public class TelecomServiceImplTest extends TelecomTestCase {
                argThat(new AnyStringIn(enforcedPermissions)), anyString());

        assertThrows(SecurityException.class,
                () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null));
                () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null, false));
    }

    @SmallTest