Loading src/java/com/android/internal/telephony/SubscriptionController.java +180 −55 Original line number Original line Diff line number Diff line Loading @@ -2338,95 +2338,220 @@ public class SubscriptionController extends ISub.Stub { * Being in the same group means they might be activated or deactivated * Being in the same group means they might be activated or deactivated * together, some of them may be invisible to the users, etc. * together, some of them may be invisible to the users, etc. * * * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE} * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or can manage all subscriptions in the list, according to their * permission or had carrier privilege permission on the subscriptions: * access rules. * {@link TelephonyManager#hasCarrierPrivileges(int)} or * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} * * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subIdList list of subId that will be in the same group * @return groupUUID a UUID assigned to the subscription group. It returns * @return groupUUID a UUID assigned to the subscription group. It returns * null if fails. * null if fails. * * */ */ @Override @Override public String setSubscriptionGroup(int[] subIdList, String callingPackage) { public String setSubscriptionGroup(int[] subIdList, String callingPackage) { boolean hasModifyPermission = mContext.checkCallingOrSelfPermission( if (subIdList == null || subIdList.length == 0) { android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED; return null; } // If it doesn't have modify phone state permission, or carrier privilege permission, // a SecurityException will be thrown. If it's due to invalid parameter or internal state, // it will return null. if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList( subIdList, callingPackage)) { return null; } long identity = Binder.clearCallingIdentity(); try { // Generate a UUID. String groupUUID = UUID.randomUUID().toString(); ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, groupUUID); int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); // If caller doesn't have modify permission or carrier privilege permission on certain refreshCachedActiveSubscriptionInfoList(); // subscriptions, maybe because the they are not active. So we keep them in a hashset and // later check access rules in our database to know whether they can manage them. return groupUUID; Set<Integer> subIdCheckList = new HashSet<>(); } finally { Binder.restoreCallingIdentity(identity); } } /** * Remove a list of subscriptions from their subscription group. * See {@link #setSubscriptionGroup(int[], String)} for more details. * * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or had carrier privilege permission on the subscriptions: * {@link TelephonyManager#hasCarrierPrivileges(int)} or * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subIdList list of subId that need removing from their groups. * @return whether the operation succeeds. * */ public boolean removeSubscriptionsFromGroup(int[] subIdList, String callingPackage) { if (subIdList == null || subIdList.length == 0) { return false; } // If it doesn't have modify phone state permission, or carrier privilege permission, // a SecurityException will be thrown. If it's due to invalid parameter or internal state, // it will return null. if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList( subIdList, callingPackage)) { return false; } long identity = Binder.clearCallingIdentity(); try { ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, (String) null); int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); refreshCachedActiveSubscriptionInfoList(); return result != 0; } finally { Binder.restoreCallingIdentity(identity); } } /** * Helper function to check if the caller has carrier privilege permissions on a list of subId. * The check can either be processed against access rules on currently active SIM cards, or * the access rules we keep in our database for currently inactive eSIMs. * * Throws {@link SecurityException} if it fails. * * @return true if checking passes on all subId. false if subId is invalid or doesn't exist, * or sub controller is not ready yet. */ private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) { mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); // Check carrier privilege permission on active subscriptions first. // If it fails, they could be inactive. So keep them in a HashSet and later check // access rules in our database. Set<Integer> checkSubList = new HashSet<>(); for (int subId : subIdList) { for (int subId : subIdList) { if (isActiveSubId(subId)) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { subIdCheckList.add(subId); throw new SecurityException("Need carrier privilege on subId " + subId); } } else { checkSubList.add(subId); } } } } if (checkSubList.isEmpty()) { return true; } long identity = Binder.clearCallingIdentity(); long identity = Binder.clearCallingIdentity(); try { try { if (!isSubInfoReady()) { if (!isSubInfoReady()) { if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); return null; return false; } } // Check access rules for each sub info. SubscriptionManager subscriptionManager = (SubscriptionManager) SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); List<SubscriptionInfo> subList = getSubInfo(null, null); List<SubscriptionInfo> subList = getSubInfo(getSelectionForSubIdList(subIdList), null); for (SubscriptionInfo subInfo : subList) { for (SubscriptionInfo subInfo : subList) { if (subIdCheckList.contains(subInfo.getSubscriptionId())) { if (checkSubList.contains(subInfo.getSubscriptionId())) { // If caller doesn't have modify permission or privilege access to if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription( // the subscription, operation is invalid and returns null. subInfo, callingPackage)) { if (hasModifyPermission || (subInfo.isEmbedded() checkSubList.remove(subInfo.getSubscriptionId()); && subscriptionManager.canManageSubscription( subInfo, callingPackage))) { subIdCheckList.remove(subInfo.getSubscriptionId()); } else { } else { if (DBG) { throw new SecurityException("Need carrier privilege on subId " logdl("setSubscriptionGroup doesn't have permission on" + subInfo.getSubscriptionId()); + " subInfo " + subInfo); } } return null; } } } } return checkSubList.isEmpty(); } finally { Binder.restoreCallingIdentity(identity); } } } if (!subIdCheckList.isEmpty()) { /** // Some SubId not found. * Helper function to create selection argument of a list of subId. StringBuilder subIdNotFound = new StringBuilder(); * The result should be: "in (subId1, subId2, ...)". for (int subId : subIdCheckList) { */ subIdNotFound.append(subId + " "); private String getSelectionForSubIdList(int[] subId) { StringBuilder selection = new StringBuilder(); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); selection.append(" IN ("); for (int i = 0; i < subId.length - 1; i++) { selection.append(subId[i] + ", "); } } if (DBG) { selection.append(subId[subId.length - 1]); logdl("setSubscriptionGroup subId not existed: " selection.append(")"); + subIdNotFound.toString()); return selection.toString(); } } /** * Get subscriptionInfo list of subscriptions that are in the same group of given subId. * See {@link #setSubscriptionGroup(int[], String)} for more details. * * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} * permission or had carrier privilege permission on the subscription. * {@link TelephonyManager#hasCarrierPrivileges(int)} * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subId of which list of subInfo from the same group will be returned. * @return list of subscriptionInfo that belong to the same group, including the given * subscription itself. It will return null if the subscription doesn't exist or it * doesn't belong to any group. * */ public List<SubscriptionInfo> getSubscriptionsInGroup(int subId, String callingPackage) { if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( mContext, subId, callingPackage, "getSubscriptionsInGroup")) { return null; return null; } } // Generate a UUID. long identity = Binder.clearCallingIdentity(); String groupUUID = UUID.randomUUID().toString(); // Selection should be: "in (subId1, subId2, ...)". try { StringBuilder selection = new StringBuilder(); SubscriptionInfo info = getActiveSubscriptionInfo(subId, callingPackage); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); if (info == null || TextUtils.isEmpty(info.getGroupUuid())) { selection.append(" IN ("); return null; for (int i = 0; i < subIdList.length - 1; i++) { selection.append(subIdList[i] + ", "); } } selection.append(subIdList[subIdList.length - 1]); selection.append(")"); ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, groupUUID); int result = mContext.getContentResolver().update( SubscriptionManager.CONTENT_URI, value, selection.toString(), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); String groupUuid = info.getGroupUuid(); List<SubscriptionInfo> infoList = getAvailableSubscriptionInfoList(callingPackage); refreshCachedActiveSubscriptionInfoList(); // Shouldn't happen because we've verified the subId belongs to an active subscription. if (infoList == null) { return null; } return groupUUID; return infoList.stream().filter( subscriptionInfo -> groupUuid.equals(subscriptionInfo.getGroupUuid())) .collect(Collectors.toList()); } finally { } finally { Binder.restoreCallingIdentity(identity); Binder.restoreCallingIdentity(identity); } } Loading tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -590,6 +590,8 @@ public class ContextFixture implements TestFixture<Context> { //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt()); //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt()); doReturn(mBundle).when(mCarrierConfigManager).getConfig(); doReturn(mBundle).when(mCarrierConfigManager).getConfig(); doReturn(true).when(mEuiccManager).isEnabled(); mConfiguration.locale = Locale.US; mConfiguration.locale = Locale.US; doReturn(mConfiguration).when(mResources).getConfiguration(); doReturn(mConfiguration).when(mResources).getConfiguration(); Loading tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +68 −23 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; Loading Loading @@ -417,14 +418,17 @@ public class SubscriptionControllerTest extends TelephonyTest { mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. try { String groupId = mSubscriptionControllerUT.setSubscriptionGroup( mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission."); } catch (SecurityException e) { // Expected result. } // With modify permission it should succeed. // With modify permission it should succeed. mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); assertNotEquals(null, groupId); Loading @@ -433,13 +437,6 @@ public class SubscriptionControllerTest extends TelephonyTest { subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, newGroupId); assertNotEquals(null, newGroupId); assertNotEquals(groupId, newGroupId); assertNotEquals(groupId, newGroupId); // SubId 6 doesn't exist. Should fail. subIdList = new int[] {1, 6}; mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); } } @Test @Test Loading @@ -447,7 +444,7 @@ public class SubscriptionControllerTest extends TelephonyTest { public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { testInsertSim(); testInsertSim(); // Adding a second profile and mark as embedded. // Adding a second profile and mark as embedded. mSubscriptionControllerUT.addSubInfoRecord("test2", 0); mSubscriptionControllerUT.addSubInfoRecord("test2", 1); ContentValues values = new ContentValues(); ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 1); values.put(SubscriptionManager.IS_EMBEDDED, 1); mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, Loading @@ -459,26 +456,38 @@ public class SubscriptionControllerTest extends TelephonyTest { int[] subIdList = new int[] {1, 2}; int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( try { mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission."); } catch (SecurityException e) { // Expected result. } // With modify permission it should succeed. doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); groupId = mSubscriptionControllerUT.setSubscriptionGroup( try { mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission on sub 2."); } catch (SecurityException e) { // Expected result. } doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); groupId = mSubscriptionControllerUT.setSubscriptionGroup( String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); assertNotEquals(null, groupId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT .getActiveSubscriptionInfoList(mContext.getOpPackageName()); .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // Revoke carrier privilege of sub 2 but make it manageable by caller. // Put sub3 into slot 1 to make sub2 inactive. doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2); mContextFixture.addCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE); mSubscriptionControllerUT.addSubInfoRecord("test3", 1); mContextFixture.removeCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE); // As sub2 is inactive, it will checks carrier privilege against access rules in the db. doReturn(true).when(mSubscriptionManager).canManageSubscription( doReturn(true).when(mSubscriptionManager).canManageSubscription( eq(subInfoList.get(1)), anyString()); eq(subInfoList.get(1)), anyString()); Loading Loading @@ -536,6 +545,42 @@ public class SubscriptionControllerTest extends TelephonyTest { assertEquals(true, opptSubList.get(0).isGroupDisabled()); assertEquals(true, opptSubList.get(0).isGroupDisabled()); } } @Test @SmallTest public void testSetSubscriptionGroup() throws Exception { testInsertSim(); // Adding a second profile and mark as embedded. mSubscriptionControllerUT.addSubInfoRecord("test2", 0); ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 1); mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); int[] subIdList = new int[] {1, 2}; String groupUuid = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupUuid); // Sub 1 and sub 2 should be in same group. List<SubscriptionInfo> infoList = mSubscriptionControllerUT .getSubscriptionsInGroup(1, mContext.getOpPackageName()); assertNotEquals(null, infoList); assertEquals(2, infoList.size()); assertEquals(1, infoList.get(0).getSubscriptionId()); assertEquals(2, infoList.get(1).getSubscriptionId()); // Remove group of sub 1. subIdList = new int[] {1}; boolean result = mSubscriptionControllerUT.removeSubscriptionsFromGroup( subIdList, mContext.getOpPackageName()); assertEquals(true, result); infoList = mSubscriptionControllerUT .getSubscriptionsInGroup(2, mContext.getOpPackageName()); assertEquals(1, infoList.size()); assertEquals(2, infoList.get(0).getSubscriptionId()); } private void registerMockTelephonyRegistry() { private void registerMockTelephonyRegistry() { mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock); mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock); doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock) doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock) Loading Loading
src/java/com/android/internal/telephony/SubscriptionController.java +180 −55 Original line number Original line Diff line number Diff line Loading @@ -2338,95 +2338,220 @@ public class SubscriptionController extends ISub.Stub { * Being in the same group means they might be activated or deactivated * Being in the same group means they might be activated or deactivated * together, some of them may be invisible to the users, etc. * together, some of them may be invisible to the users, etc. * * * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE} * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or can manage all subscriptions in the list, according to their * permission or had carrier privilege permission on the subscriptions: * access rules. * {@link TelephonyManager#hasCarrierPrivileges(int)} or * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} * * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subIdList list of subId that will be in the same group * @return groupUUID a UUID assigned to the subscription group. It returns * @return groupUUID a UUID assigned to the subscription group. It returns * null if fails. * null if fails. * * */ */ @Override @Override public String setSubscriptionGroup(int[] subIdList, String callingPackage) { public String setSubscriptionGroup(int[] subIdList, String callingPackage) { boolean hasModifyPermission = mContext.checkCallingOrSelfPermission( if (subIdList == null || subIdList.length == 0) { android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED; return null; } // If it doesn't have modify phone state permission, or carrier privilege permission, // a SecurityException will be thrown. If it's due to invalid parameter or internal state, // it will return null. if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList( subIdList, callingPackage)) { return null; } long identity = Binder.clearCallingIdentity(); try { // Generate a UUID. String groupUUID = UUID.randomUUID().toString(); ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, groupUUID); int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); // If caller doesn't have modify permission or carrier privilege permission on certain refreshCachedActiveSubscriptionInfoList(); // subscriptions, maybe because the they are not active. So we keep them in a hashset and // later check access rules in our database to know whether they can manage them. return groupUUID; Set<Integer> subIdCheckList = new HashSet<>(); } finally { Binder.restoreCallingIdentity(identity); } } /** * Remove a list of subscriptions from their subscription group. * See {@link #setSubscriptionGroup(int[], String)} for more details. * * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE} * permission or had carrier privilege permission on the subscriptions: * {@link TelephonyManager#hasCarrierPrivileges(int)} or * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)} * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subIdList list of subId that need removing from their groups. * @return whether the operation succeeds. * */ public boolean removeSubscriptionsFromGroup(int[] subIdList, String callingPackage) { if (subIdList == null || subIdList.length == 0) { return false; } // If it doesn't have modify phone state permission, or carrier privilege permission, // a SecurityException will be thrown. If it's due to invalid parameter or internal state, // it will return null. if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList( subIdList, callingPackage)) { return false; } long identity = Binder.clearCallingIdentity(); try { ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, (String) null); int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); refreshCachedActiveSubscriptionInfoList(); return result != 0; } finally { Binder.restoreCallingIdentity(identity); } } /** * Helper function to check if the caller has carrier privilege permissions on a list of subId. * The check can either be processed against access rules on currently active SIM cards, or * the access rules we keep in our database for currently inactive eSIMs. * * Throws {@link SecurityException} if it fails. * * @return true if checking passes on all subId. false if subId is invalid or doesn't exist, * or sub controller is not ready yet. */ private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) { mAppOps.checkPackage(Binder.getCallingUid(), callingPackage); // Check carrier privilege permission on active subscriptions first. // If it fails, they could be inactive. So keep them in a HashSet and later check // access rules in our database. Set<Integer> checkSubList = new HashSet<>(); for (int subId : subIdList) { for (int subId : subIdList) { if (isActiveSubId(subId)) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { subIdCheckList.add(subId); throw new SecurityException("Need carrier privilege on subId " + subId); } } else { checkSubList.add(subId); } } } } if (checkSubList.isEmpty()) { return true; } long identity = Binder.clearCallingIdentity(); long identity = Binder.clearCallingIdentity(); try { try { if (!isSubInfoReady()) { if (!isSubInfoReady()) { if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); return null; return false; } } // Check access rules for each sub info. SubscriptionManager subscriptionManager = (SubscriptionManager) SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); List<SubscriptionInfo> subList = getSubInfo(null, null); List<SubscriptionInfo> subList = getSubInfo(getSelectionForSubIdList(subIdList), null); for (SubscriptionInfo subInfo : subList) { for (SubscriptionInfo subInfo : subList) { if (subIdCheckList.contains(subInfo.getSubscriptionId())) { if (checkSubList.contains(subInfo.getSubscriptionId())) { // If caller doesn't have modify permission or privilege access to if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription( // the subscription, operation is invalid and returns null. subInfo, callingPackage)) { if (hasModifyPermission || (subInfo.isEmbedded() checkSubList.remove(subInfo.getSubscriptionId()); && subscriptionManager.canManageSubscription( subInfo, callingPackage))) { subIdCheckList.remove(subInfo.getSubscriptionId()); } else { } else { if (DBG) { throw new SecurityException("Need carrier privilege on subId " logdl("setSubscriptionGroup doesn't have permission on" + subInfo.getSubscriptionId()); + " subInfo " + subInfo); } } return null; } } } } return checkSubList.isEmpty(); } finally { Binder.restoreCallingIdentity(identity); } } } if (!subIdCheckList.isEmpty()) { /** // Some SubId not found. * Helper function to create selection argument of a list of subId. StringBuilder subIdNotFound = new StringBuilder(); * The result should be: "in (subId1, subId2, ...)". for (int subId : subIdCheckList) { */ subIdNotFound.append(subId + " "); private String getSelectionForSubIdList(int[] subId) { StringBuilder selection = new StringBuilder(); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); selection.append(" IN ("); for (int i = 0; i < subId.length - 1; i++) { selection.append(subId[i] + ", "); } } if (DBG) { selection.append(subId[subId.length - 1]); logdl("setSubscriptionGroup subId not existed: " selection.append(")"); + subIdNotFound.toString()); return selection.toString(); } } /** * Get subscriptionInfo list of subscriptions that are in the same group of given subId. * See {@link #setSubscriptionGroup(int[], String)} for more details. * * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE} * permission or had carrier privilege permission on the subscription. * {@link TelephonyManager#hasCarrierPrivileges(int)} * * @throws SecurityException if the caller doesn't meet the requirements * outlined above. * * @param subId of which list of subInfo from the same group will be returned. * @return list of subscriptionInfo that belong to the same group, including the given * subscription itself. It will return null if the subscription doesn't exist or it * doesn't belong to any group. * */ public List<SubscriptionInfo> getSubscriptionsInGroup(int subId, String callingPackage) { if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( mContext, subId, callingPackage, "getSubscriptionsInGroup")) { return null; return null; } } // Generate a UUID. long identity = Binder.clearCallingIdentity(); String groupUUID = UUID.randomUUID().toString(); // Selection should be: "in (subId1, subId2, ...)". try { StringBuilder selection = new StringBuilder(); SubscriptionInfo info = getActiveSubscriptionInfo(subId, callingPackage); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); if (info == null || TextUtils.isEmpty(info.getGroupUuid())) { selection.append(" IN ("); return null; for (int i = 0; i < subIdList.length - 1; i++) { selection.append(subIdList[i] + ", "); } } selection.append(subIdList[subIdList.length - 1]); selection.append(")"); ContentValues value = new ContentValues(); value.put(SubscriptionManager.GROUP_UUID, groupUUID); int result = mContext.getContentResolver().update( SubscriptionManager.CONTENT_URI, value, selection.toString(), null); if (DBG) logdl("setSubscriptionGroup update DB result: " + result); String groupUuid = info.getGroupUuid(); List<SubscriptionInfo> infoList = getAvailableSubscriptionInfoList(callingPackage); refreshCachedActiveSubscriptionInfoList(); // Shouldn't happen because we've verified the subId belongs to an active subscription. if (infoList == null) { return null; } return groupUUID; return infoList.stream().filter( subscriptionInfo -> groupUuid.equals(subscriptionInfo.getGroupUuid())) .collect(Collectors.toList()); } finally { } finally { Binder.restoreCallingIdentity(identity); Binder.restoreCallingIdentity(identity); } } Loading
tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -590,6 +590,8 @@ public class ContextFixture implements TestFixture<Context> { //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt()); //doReturn(mBundle).when(mCarrierConfigManager).getConfig(anyInt()); doReturn(mBundle).when(mCarrierConfigManager).getConfig(); doReturn(mBundle).when(mCarrierConfigManager).getConfig(); doReturn(true).when(mEuiccManager).isEnabled(); mConfiguration.locale = Locale.US; mConfiguration.locale = Locale.US; doReturn(mConfiguration).when(mResources).getConfiguration(); doReturn(mConfiguration).when(mResources).getConfiguration(); Loading
tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +68 −23 Original line number Original line Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; Loading Loading @@ -417,14 +418,17 @@ public class SubscriptionControllerTest extends TelephonyTest { mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. try { String groupId = mSubscriptionControllerUT.setSubscriptionGroup( mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission."); } catch (SecurityException e) { // Expected result. } // With modify permission it should succeed. // With modify permission it should succeed. mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); assertNotEquals(null, groupId); Loading @@ -433,13 +437,6 @@ public class SubscriptionControllerTest extends TelephonyTest { subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, newGroupId); assertNotEquals(null, newGroupId); assertNotEquals(groupId, newGroupId); assertNotEquals(groupId, newGroupId); // SubId 6 doesn't exist. Should fail. subIdList = new int[] {1, 6}; mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); } } @Test @Test Loading @@ -447,7 +444,7 @@ public class SubscriptionControllerTest extends TelephonyTest { public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { public void testSetSubscriptionGroupWithCarrierPrivilegePermission() throws Exception { testInsertSim(); testInsertSim(); // Adding a second profile and mark as embedded. // Adding a second profile and mark as embedded. mSubscriptionControllerUT.addSubInfoRecord("test2", 0); mSubscriptionControllerUT.addSubInfoRecord("test2", 1); ContentValues values = new ContentValues(); ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 1); values.put(SubscriptionManager.IS_EMBEDDED, 1); mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, Loading @@ -459,26 +456,38 @@ public class SubscriptionControllerTest extends TelephonyTest { int[] subIdList = new int[] {1, 2}; int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( try { mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission."); } catch (SecurityException e) { // Expected result. } // With modify permission it should succeed. doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); groupId = mSubscriptionControllerUT.setSubscriptionGroup( try { mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); fail("setSubscriptionGroup should fail with no permission on sub 2."); } catch (SecurityException e) { // Expected result. } doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); groupId = mSubscriptionControllerUT.setSubscriptionGroup( String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); assertNotEquals(null, groupId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT .getActiveSubscriptionInfoList(mContext.getOpPackageName()); .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // Revoke carrier privilege of sub 2 but make it manageable by caller. // Put sub3 into slot 1 to make sub2 inactive. doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2); mContextFixture.addCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE); mSubscriptionControllerUT.addSubInfoRecord("test3", 1); mContextFixture.removeCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE); // As sub2 is inactive, it will checks carrier privilege against access rules in the db. doReturn(true).when(mSubscriptionManager).canManageSubscription( doReturn(true).when(mSubscriptionManager).canManageSubscription( eq(subInfoList.get(1)), anyString()); eq(subInfoList.get(1)), anyString()); Loading Loading @@ -536,6 +545,42 @@ public class SubscriptionControllerTest extends TelephonyTest { assertEquals(true, opptSubList.get(0).isGroupDisabled()); assertEquals(true, opptSubList.get(0).isGroupDisabled()); } } @Test @SmallTest public void testSetSubscriptionGroup() throws Exception { testInsertSim(); // Adding a second profile and mark as embedded. mSubscriptionControllerUT.addSubInfoRecord("test2", 0); ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 1); mFakeTelephonyProvider.update(SubscriptionManager.CONTENT_URI, values, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + 2, null); mSubscriptionControllerUT.refreshCachedActiveSubscriptionInfoList(); int[] subIdList = new int[] {1, 2}; String groupUuid = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupUuid); // Sub 1 and sub 2 should be in same group. List<SubscriptionInfo> infoList = mSubscriptionControllerUT .getSubscriptionsInGroup(1, mContext.getOpPackageName()); assertNotEquals(null, infoList); assertEquals(2, infoList.size()); assertEquals(1, infoList.get(0).getSubscriptionId()); assertEquals(2, infoList.get(1).getSubscriptionId()); // Remove group of sub 1. subIdList = new int[] {1}; boolean result = mSubscriptionControllerUT.removeSubscriptionsFromGroup( subIdList, mContext.getOpPackageName()); assertEquals(true, result); infoList = mSubscriptionControllerUT .getSubscriptionsInGroup(2, mContext.getOpPackageName()); assertEquals(1, infoList.size()); assertEquals(2, infoList.get(0).getSubscriptionId()); } private void registerMockTelephonyRegistry() { private void registerMockTelephonyRegistry() { mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock); mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegisteryMock); doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock) doReturn(mTelephonyRegisteryMock).when(mTelephonyRegisteryMock) Loading