Loading src/java/com/android/internal/telephony/SubscriptionController.java +162 −76 Original line number Diff line number Diff line Loading @@ -284,6 +284,24 @@ public class SubscriptionController extends ISub.Stub { Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message); } /** * Returns whether the {@code callingPackage} has access to subscriber identifiers on the * specified {@code subId} using the provided {@code message} in any resulting * SecurityException. */ private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage, String message) { try { return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId, callingPackage, message); } catch (SecurityException e) { // A SecurityException indicates that the calling package is targeting at least the // minimum level that enforces identifier access restrictions and the new access // requirements are not met. return false; } } /** * Broadcast when SubscriptionInfo has changed * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener Loading Loading @@ -515,17 +533,20 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); List<SubscriptionInfo> subList; try { List<SubscriptionInfo> subList = getActiveSubscriptionInfoList( mContext.getOpPackageName()); subList = getActiveSubscriptionInfoList(mContext.getOpPackageName()); } finally { Binder.restoreCallingIdentity(identity); } if (subList != null) { for (SubscriptionInfo si : subList) { if (si.getSubscriptionId() == subId) { if (DBG) { if (VDBG) { logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si); } return si; return conditionallyRemoveIdentifiers(si, callingPackage, "getActiveSubscriptionInfo"); } } } Loading @@ -533,9 +554,6 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfo]- subId=" + subId + " subList=" + subList + " subInfo=null"); } } finally { Binder.restoreCallingIdentity(identity); } return null; } Loading Loading @@ -634,9 +652,12 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); List<SubscriptionInfo> subList; try { List<SubscriptionInfo> subList = getActiveSubscriptionInfoList( mContext.getOpPackageName()); subList = getActiveSubscriptionInfoList(mContext.getOpPackageName()); } finally { Binder.restoreCallingIdentity(identity); } if (subList != null) { for (SubscriptionInfo si : subList) { if (si.getSimSlotIndex() == slotIndex) { Loading @@ -644,7 +665,8 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex + " subId=" + si); } return si; return conditionallyRemoveIdentifiers(si, callingPackage, "getActiveSubscriptionInfoForSimSlotIndex"); } } if (DBG) { Loading @@ -656,9 +678,7 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null"); } } } finally { Binder.restoreCallingIdentity(identity); } return null; } Loading Loading @@ -697,6 +717,12 @@ public class SubscriptionController extends ISub.Stub { } } private List<SubscriptionInfo> makeCacheListCopyWithLock(List<SubscriptionInfo> cacheSubList) { synchronized (mSubInfoListLock) { return new ArrayList<>(cacheSubList); } } /** * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local * and remote SIMs. Loading @@ -706,7 +732,8 @@ public class SubscriptionController extends ISub.Stub { @UnsupportedAppUsage @Override public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) { return getSubscriptionInfoListFromCacheHelper(callingPackage, mCacheActiveSubInfoList); return getSubscriptionInfoListFromCacheHelper(callingPackage, makeCacheListCopyWithLock(mCacheActiveSubInfoList)); } /** Loading @@ -717,13 +744,13 @@ public class SubscriptionController extends ISub.Stub { public void refreshCachedActiveSubscriptionInfoList() { boolean opptSubListChanged; synchronized (mSubInfoListLock) { List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo( SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " + SubscriptionManager.SUBSCRIPTION_TYPE + "=" + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM, null); synchronized (mSubInfoListLock) { if (activeSubscriptionInfoList != null) { // Log when active sub info changes. if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size() Loading @@ -738,10 +765,6 @@ public class SubscriptionController extends ISub.Stub { logd("activeSubscriptionInfoList is null."); mCacheActiveSubInfoList.clear(); } // Refresh cached opportunistic sub list and detect whether it's changed. refreshCachedOpportunisticSubscriptionInfoList(); if (DBG_CACHE) { if (!mCacheActiveSubInfoList.isEmpty()) { for (SubscriptionInfo si : mCacheActiveSubInfoList) { Loading @@ -753,6 +776,9 @@ public class SubscriptionController extends ISub.Stub { } } } // Refresh cached opportunistic sub list and detect whether it's changed. refreshCachedOpportunisticSubscriptionInfoList(); } /** Loading Loading @@ -2965,8 +2991,8 @@ public class SubscriptionController extends ISub.Stub { @Override public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage) { return getSubscriptionInfoListFromCacheHelper( callingPackage, mCacheOpportunisticSubInfoList); return getSubscriptionInfoListFromCacheHelper(callingPackage, makeCacheListCopyWithLock(mCacheOpportunisticSubInfoList)); } /** Loading Loading @@ -3334,7 +3360,9 @@ public class SubscriptionController extends ISub.Stub { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage, "getSubscriptionsInGroup") || info.canManageSubscription(mContext, callingPackage); }).collect(Collectors.toList()); }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, callingPackage, "getSubscriptionsInGroup")) .collect(Collectors.toList()); } public ParcelUuid getGroupUuid(int subId) { Loading Loading @@ -3601,34 +3629,93 @@ public class SubscriptionController extends ISub.Stub { // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( String callingPackage, List<SubscriptionInfo> cacheSubList) { boolean canReadAllPhoneState; boolean canReadPhoneState = false; boolean canReadIdentifiers = false; try { canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, "getSubscriptionInfoList"); // If the calling package has the READ_PHONE_STATE permission then check if the caller // also has access to subscriber identifiers to ensure that the ICC // ID and any other unique identifiers are removed if the caller should not have access. if (canReadPhoneState) { canReadIdentifiers = hasSubscriberIdentifierAccess( SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, "getSubscriptionInfoList"); } } catch (SecurityException e) { canReadAllPhoneState = false; // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way // to access a subscription is to have carrier privileges for its subId; an app with // carrier privileges for a subscription is also granted access to all identifiers so // the identifier and phone number access checks are not required. } synchronized (mSubInfoListLock) { // If the caller can read all phone state, just return the full list. if (canReadAllPhoneState) { return new ArrayList<>(cacheSubList); if (canReadIdentifiers) { return cacheSubList; } // Filter the list to only include subscriptions which the caller can manage. return cacheSubList.stream() .filter(subscriptionInfo -> { try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, "getSubscriptionInfoList"); } catch (SecurityException e) { return false; List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size()); for (SubscriptionInfo subscriptionInfo : cacheSubList) { int subId = subscriptionInfo.getSubscriptionId(); boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId( subId); // If the caller does not have the READ_PHONE_STATE permission nor carrier // privileges then they cannot access the current subscription. if (!canReadPhoneState && !hasCarrierPrivileges) { continue; } // If the caller has carrier privileges then they are granted access to all // identifiers for their subscription. if (hasCarrierPrivileges) { subscriptions.add(subscriptionInfo); } else { // The caller does not have carrier privileges for this subId, filter the // identifiers in the subscription based on the results of the initial // permission checks. subscriptions.add( conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers)); } }) .collect(Collectors.toList()); } return subscriptions; } /** * Conditionally removes identifiers from the provided {@code subInfo} if the {@code * callingPackage} does not meet the access requirements for identifiers and returns the * potentially modified object.. * * <p>If the caller does not meet the access requirements for identifiers a clone of the * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects * in a cache. */ private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, String callingPackage, String message) { SubscriptionInfo result = subInfo; int subId = subInfo.getSubscriptionId(); boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage, message); return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess); } /** * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling * package {@code hasIdentifierAccess} and returns the potentially modified object. * * <p>If the caller specifies the package does not have identifier access * a clone of the provided SubscriptionInfo is created and modified to avoid altering * SubscriptionInfo objects in a cache. */ private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, boolean hasIdentifierAccess) { if (hasIdentifierAccess) { return subInfo; } SubscriptionInfo result = new SubscriptionInfo(subInfo); if (!hasIdentifierAccess) { result.clearIccId(); result.clearCardString(); } return result; } private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) { Loading Loading @@ -3690,13 +3777,12 @@ public class SubscriptionController extends ISub.Stub { } private void refreshCachedOpportunisticSubscriptionInfoList() { synchronized (mSubInfoListLock) { List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList; List<SubscriptionInfo> subList = getSubInfo( SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND (" + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " + SubscriptionManager.IS_EMBEDDED + "=1)", null); synchronized (mSubInfoListLock) { List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList; if (subList != null) { subList.sort(SUBSCRIPTION_INFO_COMPARATOR); Loading tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +230 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ public class SubscriptionControllerTest extends TelephonyTest { private static final String MAC_ADDRESS_PREFIX = "mac_"; private static final String DISPLAY_NAME_PREFIX = "my_phone_"; private static final String UNAVAILABLE_ICCID = ""; @Before public void setUp() throws Exception { super.setUp("SubscriptionControllerTest"); Loading Loading @@ -941,6 +943,234 @@ public class SubscriptionControllerTest extends TelephonyTest { assertTrue("active sub ids = " + subIds, Arrays.equals(subIds, new int[]{2, 1})); } @Test public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfo should throw a SecurityException; testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); int subId = getFirstSubId(); try { mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage); fail("getActiveSubscriptionInfo should fail when invoked with no permissions"); } catch (SecurityException expected) { } } @Test public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfo should still return a result but the ICC ID should not be // available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); int subId = getFirstSubId(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( subId, mCallingPackage); assertNotNull(subscriptionInfo); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); } @Test public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo. testInsertSim(); int subId = getFirstSubId(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( subId, mCallingPackage); assertNotNull(subscriptionInfo); assertTrue(subscriptionInfo.getIccId().length() > 0); assertTrue(subscriptionInfo.getCardString().length() > 0); } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a // SecurityException. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); try { mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no " + "permissions"); } catch (SecurityException expected) { } } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but // the ICC ID should not be available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); assertNotNull(subscriptionInfo); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo. testInsertSim(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); assertNotNull(subscriptionInfo); assertTrue(subscriptionInfo.getIccId().length() > 0); assertTrue(subscriptionInfo.getCardString().length() > 0); } @Test public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfoList should return a list with 0 elements. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertNotNull(subInfoList); assertTrue(subInfoList.size() == 0); } @Test public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects // but the ICC ID should not be available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertEquals(UNAVAILABLE_ICCID, info.getIccId()); assertEquals(UNAVAILABLE_ICCID, info.getCardString()); } } @Test public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess() throws Exception { // An app with access to device identifiers may not have access to the device phone number // (ie an app that passes the device / profile owner check or an app that has been granted // the device identifiers appop); this test verifies that an app with identifier access // can read the ICC ID but does not receive the phone number. testInsertSim(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test", subInfo.getIccId()); } @Test public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. testInsertSim(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertTrue(info.getIccId().length() > 0); assertTrue(info.getCardString().length() > 0); } } @Test public void testGetSubscriptionsInGroupWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getSubscriptionsInGroup should throw a SecurityException when the // READ_PHONE_STATE permission check is performed. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); try { mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage); fail("getSubscriptionsInGroup should fail when invoked with no permissions"); } catch (SecurityException expected) { } } @Test public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getSubscriptionsInGroup should still return the list of SubscriptionInfo objects // but the ICC ID should not be available via getIccId or getCardString. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( groupUuid, mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertEquals(UNAVAILABLE_ICCID, info.getIccId()); assertEquals(UNAVAILABLE_ICCID, info.getCardString()); } } @Test public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( groupUuid, mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertTrue(info.getIccId().length() > 0); assertTrue(info.getCardString().length() > 0); } } private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception { testInsertSim(); int[] subIdList = new int[]{getFirstSubId()}; ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList, mCallingPackage); assertNotNull(groupUuid); return groupUuid; } private int getFirstSubId() throws Exception { return getSubIdAtIndex(0); } private int getSubIdAtIndex(int index) throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); assertTrue(subIds != null && subIds.length > index); return subIds[index]; } @Test public void testGetEnabledSubscriptionIdSingleSIM() { // A single SIM device may have logical slot 0 mapped to physical slot 1 Loading tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +9 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/java/com/android/internal/telephony/SubscriptionController.java +162 −76 Original line number Diff line number Diff line Loading @@ -284,6 +284,24 @@ public class SubscriptionController extends ISub.Stub { Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message); } /** * Returns whether the {@code callingPackage} has access to subscriber identifiers on the * specified {@code subId} using the provided {@code message} in any resulting * SecurityException. */ private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage, String message) { try { return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId, callingPackage, message); } catch (SecurityException e) { // A SecurityException indicates that the calling package is targeting at least the // minimum level that enforces identifier access restrictions and the new access // requirements are not met. return false; } } /** * Broadcast when SubscriptionInfo has changed * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener Loading Loading @@ -515,17 +533,20 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); List<SubscriptionInfo> subList; try { List<SubscriptionInfo> subList = getActiveSubscriptionInfoList( mContext.getOpPackageName()); subList = getActiveSubscriptionInfoList(mContext.getOpPackageName()); } finally { Binder.restoreCallingIdentity(identity); } if (subList != null) { for (SubscriptionInfo si : subList) { if (si.getSubscriptionId() == subId) { if (DBG) { if (VDBG) { logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si); } return si; return conditionallyRemoveIdentifiers(si, callingPackage, "getActiveSubscriptionInfo"); } } } Loading @@ -533,9 +554,6 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfo]- subId=" + subId + " subList=" + subList + " subInfo=null"); } } finally { Binder.restoreCallingIdentity(identity); } return null; } Loading Loading @@ -634,9 +652,12 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); List<SubscriptionInfo> subList; try { List<SubscriptionInfo> subList = getActiveSubscriptionInfoList( mContext.getOpPackageName()); subList = getActiveSubscriptionInfoList(mContext.getOpPackageName()); } finally { Binder.restoreCallingIdentity(identity); } if (subList != null) { for (SubscriptionInfo si : subList) { if (si.getSimSlotIndex() == slotIndex) { Loading @@ -644,7 +665,8 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex + " subId=" + si); } return si; return conditionallyRemoveIdentifiers(si, callingPackage, "getActiveSubscriptionInfoForSimSlotIndex"); } } if (DBG) { Loading @@ -656,9 +678,7 @@ public class SubscriptionController extends ISub.Stub { logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null"); } } } finally { Binder.restoreCallingIdentity(identity); } return null; } Loading Loading @@ -697,6 +717,12 @@ public class SubscriptionController extends ISub.Stub { } } private List<SubscriptionInfo> makeCacheListCopyWithLock(List<SubscriptionInfo> cacheSubList) { synchronized (mSubInfoListLock) { return new ArrayList<>(cacheSubList); } } /** * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local * and remote SIMs. Loading @@ -706,7 +732,8 @@ public class SubscriptionController extends ISub.Stub { @UnsupportedAppUsage @Override public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) { return getSubscriptionInfoListFromCacheHelper(callingPackage, mCacheActiveSubInfoList); return getSubscriptionInfoListFromCacheHelper(callingPackage, makeCacheListCopyWithLock(mCacheActiveSubInfoList)); } /** Loading @@ -717,13 +744,13 @@ public class SubscriptionController extends ISub.Stub { public void refreshCachedActiveSubscriptionInfoList() { boolean opptSubListChanged; synchronized (mSubInfoListLock) { List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo( SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " + SubscriptionManager.SUBSCRIPTION_TYPE + "=" + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM, null); synchronized (mSubInfoListLock) { if (activeSubscriptionInfoList != null) { // Log when active sub info changes. if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size() Loading @@ -738,10 +765,6 @@ public class SubscriptionController extends ISub.Stub { logd("activeSubscriptionInfoList is null."); mCacheActiveSubInfoList.clear(); } // Refresh cached opportunistic sub list and detect whether it's changed. refreshCachedOpportunisticSubscriptionInfoList(); if (DBG_CACHE) { if (!mCacheActiveSubInfoList.isEmpty()) { for (SubscriptionInfo si : mCacheActiveSubInfoList) { Loading @@ -753,6 +776,9 @@ public class SubscriptionController extends ISub.Stub { } } } // Refresh cached opportunistic sub list and detect whether it's changed. refreshCachedOpportunisticSubscriptionInfoList(); } /** Loading Loading @@ -2965,8 +2991,8 @@ public class SubscriptionController extends ISub.Stub { @Override public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage) { return getSubscriptionInfoListFromCacheHelper( callingPackage, mCacheOpportunisticSubInfoList); return getSubscriptionInfoListFromCacheHelper(callingPackage, makeCacheListCopyWithLock(mCacheOpportunisticSubInfoList)); } /** Loading Loading @@ -3334,7 +3360,9 @@ public class SubscriptionController extends ISub.Stub { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage, "getSubscriptionsInGroup") || info.canManageSubscription(mContext, callingPackage); }).collect(Collectors.toList()); }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, callingPackage, "getSubscriptionsInGroup")) .collect(Collectors.toList()); } public ParcelUuid getGroupUuid(int subId) { Loading Loading @@ -3601,34 +3629,93 @@ public class SubscriptionController extends ISub.Stub { // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( String callingPackage, List<SubscriptionInfo> cacheSubList) { boolean canReadAllPhoneState; boolean canReadPhoneState = false; boolean canReadIdentifiers = false; try { canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, "getSubscriptionInfoList"); // If the calling package has the READ_PHONE_STATE permission then check if the caller // also has access to subscriber identifiers to ensure that the ICC // ID and any other unique identifiers are removed if the caller should not have access. if (canReadPhoneState) { canReadIdentifiers = hasSubscriberIdentifierAccess( SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, "getSubscriptionInfoList"); } } catch (SecurityException e) { canReadAllPhoneState = false; // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way // to access a subscription is to have carrier privileges for its subId; an app with // carrier privileges for a subscription is also granted access to all identifiers so // the identifier and phone number access checks are not required. } synchronized (mSubInfoListLock) { // If the caller can read all phone state, just return the full list. if (canReadAllPhoneState) { return new ArrayList<>(cacheSubList); if (canReadIdentifiers) { return cacheSubList; } // Filter the list to only include subscriptions which the caller can manage. return cacheSubList.stream() .filter(subscriptionInfo -> { try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, "getSubscriptionInfoList"); } catch (SecurityException e) { return false; List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size()); for (SubscriptionInfo subscriptionInfo : cacheSubList) { int subId = subscriptionInfo.getSubscriptionId(); boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId( subId); // If the caller does not have the READ_PHONE_STATE permission nor carrier // privileges then they cannot access the current subscription. if (!canReadPhoneState && !hasCarrierPrivileges) { continue; } // If the caller has carrier privileges then they are granted access to all // identifiers for their subscription. if (hasCarrierPrivileges) { subscriptions.add(subscriptionInfo); } else { // The caller does not have carrier privileges for this subId, filter the // identifiers in the subscription based on the results of the initial // permission checks. subscriptions.add( conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers)); } }) .collect(Collectors.toList()); } return subscriptions; } /** * Conditionally removes identifiers from the provided {@code subInfo} if the {@code * callingPackage} does not meet the access requirements for identifiers and returns the * potentially modified object.. * * <p>If the caller does not meet the access requirements for identifiers a clone of the * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects * in a cache. */ private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, String callingPackage, String message) { SubscriptionInfo result = subInfo; int subId = subInfo.getSubscriptionId(); boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage, message); return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess); } /** * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling * package {@code hasIdentifierAccess} and returns the potentially modified object. * * <p>If the caller specifies the package does not have identifier access * a clone of the provided SubscriptionInfo is created and modified to avoid altering * SubscriptionInfo objects in a cache. */ private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, boolean hasIdentifierAccess) { if (hasIdentifierAccess) { return subInfo; } SubscriptionInfo result = new SubscriptionInfo(subInfo); if (!hasIdentifierAccess) { result.clearIccId(); result.clearCardString(); } return result; } private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) { Loading Loading @@ -3690,13 +3777,12 @@ public class SubscriptionController extends ISub.Stub { } private void refreshCachedOpportunisticSubscriptionInfoList() { synchronized (mSubInfoListLock) { List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList; List<SubscriptionInfo> subList = getSubInfo( SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND (" + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR " + SubscriptionManager.IS_EMBEDDED + "=1)", null); synchronized (mSubInfoListLock) { List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList; if (subList != null) { subList.sort(SUBSCRIPTION_INFO_COMPARATOR); Loading
tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +230 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,8 @@ public class SubscriptionControllerTest extends TelephonyTest { private static final String MAC_ADDRESS_PREFIX = "mac_"; private static final String DISPLAY_NAME_PREFIX = "my_phone_"; private static final String UNAVAILABLE_ICCID = ""; @Before public void setUp() throws Exception { super.setUp("SubscriptionControllerTest"); Loading Loading @@ -941,6 +943,234 @@ public class SubscriptionControllerTest extends TelephonyTest { assertTrue("active sub ids = " + subIds, Arrays.equals(subIds, new int[]{2, 1})); } @Test public void testGetActiveSubscriptionInfoWithNoPermissions() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfo should throw a SecurityException; testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); int subId = getFirstSubId(); try { mSubscriptionControllerUT.getActiveSubscriptionInfo(subId, mCallingPackage); fail("getActiveSubscriptionInfo should fail when invoked with no permissions"); } catch (SecurityException expected) { } } @Test public void testGetActiveSubscriptionInfoWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfo should still return a result but the ICC ID should not be // available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); int subId = getFirstSubId(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( subId, mCallingPackage); assertNotNull(subscriptionInfo); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); } @Test public void testGetActiveSubscriptionWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo. testInsertSim(); int subId = getFirstSubId(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfo( subId, mCallingPackage); assertNotNull(subscriptionInfo); assertTrue(subscriptionInfo.getIccId().length() > 0); assertTrue(subscriptionInfo.getCardString().length() > 0); } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfoForSimSlotIndex should throw a // SecurityException. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); try { mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); fail("getActiveSubscriptionInfoForSimSlotIndex should fail when invoked with no " + "permissions"); } catch (SecurityException expected) { } } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfoForSimlSlotIndex should still return the SubscriptionInfo but // the ICC ID should not be available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); assertNotNull(subscriptionInfo); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getIccId()); assertEquals(UNAVAILABLE_ICCID, subscriptionInfo.getCardString()); } @Test public void testGetActiveSubscriptionInfoForSimSlotIndexWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo. testInsertSim(); SubscriptionInfo subscriptionInfo = mSubscriptionControllerUT.getActiveSubscriptionInfoForSimSlotIndex(0, mCallingPackage); assertNotNull(subscriptionInfo); assertTrue(subscriptionInfo.getIccId().length() > 0); assertTrue(subscriptionInfo.getCardString().length() > 0); } @Test public void testGetActiveSubscriptionInfoListWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getActiveSubscriptionInfoList should return a list with 0 elements. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertNotNull(subInfoList); assertTrue(subInfoList.size() == 0); } @Test public void testGetActiveSubscriptionInfoListWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getActiveSubscriptionInfoList should still return the list of SubscriptionInfo objects // but the ICC ID should not be available via getIccId or getCardString. testInsertSim(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertEquals(UNAVAILABLE_ICCID, info.getIccId()); assertEquals(UNAVAILABLE_ICCID, info.getCardString()); } } @Test public void testGetActiveSubscriptionInfoListWithIdentifierAccessWithoutNumberAccess() throws Exception { // An app with access to device identifiers may not have access to the device phone number // (ie an app that passes the device / profile owner check or an app that has been granted // the device identifiers appop); this test verifies that an app with identifier access // can read the ICC ID but does not receive the phone number. testInsertSim(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test", subInfo.getIccId()); } @Test public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. testInsertSim(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertTrue(info.getIccId().length() > 0); assertTrue(info.getCardString().length() > 0); } } @Test public void testGetSubscriptionsInGroupWithNoPermission() throws Exception { // If the calling package does not have the READ_PHONE_STATE permission or carrier // privileges then getSubscriptionsInGroup should throw a SecurityException when the // READ_PHONE_STATE permission check is performed. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); try { mSubscriptionControllerUT.getSubscriptionsInGroup(groupUuid, mCallingPackage); fail("getSubscriptionsInGroup should fail when invoked with no permissions"); } catch (SecurityException expected) { } } @Test public void testGetSubscriptionsInGroupWithReadPhoneState() throws Exception { // If the calling package only has the READ_PHONE_STATE permission then // getSubscriptionsInGroup should still return the list of SubscriptionInfo objects // but the ICC ID should not be available via getIccId or getCardString. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setupMocksForTelephonyPermissions(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( groupUuid, mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertEquals(UNAVAILABLE_ICCID, info.getIccId()); assertEquals(UNAVAILABLE_ICCID, info.getCardString()); } } @Test public void testGetSubscriptionsInGroupWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier // privileges the ICC ID should be available in the SubscriptionInfo objects in the List. ParcelUuid groupUuid = setupGetSubscriptionsInGroupTest(); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getSubscriptionsInGroup( groupUuid, mCallingPackage); assertTrue(subInfoList.size() > 0); for (SubscriptionInfo info : subInfoList) { assertTrue(info.getIccId().length() > 0); assertTrue(info.getCardString().length() > 0); } } private ParcelUuid setupGetSubscriptionsInGroupTest() throws Exception { testInsertSim(); int[] subIdList = new int[]{getFirstSubId()}; ParcelUuid groupUuid = mSubscriptionControllerUT.createSubscriptionGroup(subIdList, mCallingPackage); assertNotNull(groupUuid); return groupUuid; } private int getFirstSubId() throws Exception { return getSubIdAtIndex(0); } private int getSubIdAtIndex(int index) throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); assertTrue(subIds != null && subIds.length > index); return subIds[index]; } @Test public void testGetEnabledSubscriptionIdSingleSIM() { // A single SIM device may have logical slot 0 mapped to physical slot 1 Loading
tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +9 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes