Loading src/java/com/android/internal/telephony/SubscriptionController.java +111 −41 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.Manifest; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.AppOpsManager; Loading Loading @@ -63,6 +65,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Map.Entry; import java.util.Objects; import java.util.Objects; import java.util.Set; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Collectors; Loading Loading @@ -313,8 +316,8 @@ public class SubscriptionController extends ISub.Stub { } } boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow( boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow( SubscriptionManager.IS_OPPORTUNISTIC)) == 1; SubscriptionManager.IS_OPPORTUNISTIC)) == 1; int parentSubId = cursor.getInt(cursor.getColumnIndexOrThrow( String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow( SubscriptionManager.PARENT_SUB_ID)); SubscriptionManager.GROUP_UUID)); if (VDBG) { if (VDBG) { String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId); String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId); Loading @@ -325,7 +328,7 @@ public class SubscriptionController extends ISub.Stub { + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:" + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:" + isEmbedded + " accessRules:" + Arrays.toString(accessRules) + isEmbedded + " accessRules:" + Arrays.toString(accessRules) + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic + " parentSubId:" + parentSubId); + " groupUUID:" + groupUUID); } } // If line1number has been set to a different number, use it instead. // If line1number has been set to a different number, use it instead. Loading @@ -335,7 +338,7 @@ public class SubscriptionController extends ISub.Stub { } } return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardId, isOpportunistic, parentSubId); isEmbedded, accessRules, cardId, isOpportunistic, groupUUID); } } /** /** Loading Loading @@ -2025,7 +2028,6 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.CB_OPT_OUT_DIALOG: case SubscriptionManager.CB_OPT_OUT_DIALOG: case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.PARENT_SUB_ID: case SubscriptionManager.VT_IMS_ENABLED: case SubscriptionManager.VT_IMS_ENABLED: case SubscriptionManager.WFC_IMS_ENABLED: case SubscriptionManager.WFC_IMS_ENABLED: case SubscriptionManager.WFC_IMS_MODE: case SubscriptionManager.WFC_IMS_MODE: Loading Loading @@ -2085,7 +2087,7 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.WFC_IMS_ROAMING_MODE: case SubscriptionManager.WFC_IMS_ROAMING_MODE: case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.PARENT_SUB_ID: case SubscriptionManager.GROUP_UUID: resultValue = cursor.getInt(0) + ""; resultValue = cursor.getInt(0) + ""; break; break; default: default: Loading Loading @@ -2237,40 +2239,6 @@ public class SubscriptionController extends ISub.Stub { String.valueOf(opportunistic ? 1 : 0)); String.valueOf(opportunistic ? 1 : 0)); } } /** * Set parent subId (parentSubId) of another subscription (subId). * It's used in scenarios where a child ubscription is bundled with a * primary parent subscription. The child subscription will typically be opportunistic * and will be used to provide data services where available, with the parent being * the primary fallback subscription. * * @param parentSubId subId of its parent subscription. * @param subId the unique SubscriptionInfo index in database * @return the number of records updated */ @Override public int setParentSubId(int parentSubId, int subId) { enforceModifyPhoneState("setParentSubId"); final long token = Binder.clearCallingIdentity(); try { if (!SubscriptionManager.isUsableSubIdValue(parentSubId) || parentSubId > getAllSubInfoCount(mContext.getOpPackageName()) || parentSubId == subId) { if (DBG) { logd("[setParentSubId]- fail with parentSubId " + parentSubId + " subId " + subId); } return -1; } return setSubscriptionProperty(subId, SubscriptionManager.PARENT_SUB_ID, String.valueOf(parentSubId)); } finally { Binder.restoreCallingIdentity(token); } } @Override @Override public int setPreferredData(int subId) { public int setPreferredData(int subId) { enforceModifyPhoneState("setPreferredData"); enforceModifyPhoneState("setPreferredData"); Loading Loading @@ -2306,6 +2274,108 @@ public class SubscriptionController extends ISub.Stub { callingPackage, mCacheOpportunisticSubInfoList); callingPackage, mCacheOpportunisticSubInfoList); } } /** * Inform SubscriptionManager that subscriptions in the list are bundled * as a group. Typically it's a primary subscription and an opportunistic * subscription. It should only affect multi-SIM scenarios where primary * and opportunistic subscriptions can be activated together. * Being in the same group means they might be activated or deactivated * together, some of them may be invisible to the users, etc. * * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE} * permission or can manage all subscriptions in the list, according to their * access rules. * * @return groupUUID a UUID assigned to the subscription group. It returns * null if fails. * */ @Override public String setSubscriptionGroup(int[] subIdList, String callingPackage) { boolean hasModifyPermission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED; // If caller doesn't have modify permission or carrier privilege permission on certain // 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. Set<Integer> subIdCheckList = new HashSet<>(); for (int subId : subIdList) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { subIdCheckList.add(subId); } } long identity = Binder.clearCallingIdentity(); try { if (!isSubInfoReady()) { if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); return null; } SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); List<SubscriptionInfo> subList = getSubInfo(null, null); for (SubscriptionInfo subInfo : subList) { if (subIdCheckList.contains(subInfo.getSubscriptionId())) { // If caller doesn't have modify permission or privilege access to // the subscription, operation is invalid and returns null. if (hasModifyPermission || (subInfo.isEmbedded() && subscriptionManager.canManageSubscription( subInfo, callingPackage))) { subIdCheckList.remove(subInfo.getSubscriptionId()); } else { if (DBG) { logdl("setSubscriptionGroup doesn't have permission on" + " subInfo " + subInfo); } return null; } } } if (!subIdCheckList.isEmpty()) { // Some SubId not found. StringBuilder subIdNotFound = new StringBuilder(); for (int subId : subIdCheckList) { subIdNotFound.append(subId + " "); } if (DBG) { logdl("setSubscriptionGroup subId not existed: " + subIdNotFound.toString()); } return null; } // Generate a UUID. String groupUUID = UUID.randomUUID().toString(); // Selection should be: "in (subId1, subId2, ...)". StringBuilder selection = new StringBuilder(); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); selection.append(" IN ("); 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); refreshCachedActiveSubscriptionInfoList(); return groupUUID; } finally { Binder.restoreCallingIdentity(identity); } } // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList. // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList. // They are doing similar things except operating on different cache. // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( Loading Loading @@ -2336,7 +2406,7 @@ public class SubscriptionController extends ISub.Stub { try { try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, subscriptionInfo.getSubscriptionId(), callingPackage, "getOpportunisticSubscriptions"); "getSubscriptionInfoList"); } catch (SecurityException e) { } catch (SecurityException e) { return false; return false; } } Loading tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -97,7 +97,7 @@ public class FakeTelephonyProvider extends MockContentProvider { + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1," + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0," + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0," + SubscriptionManager.PARENT_SUB_ID + " INTEGER DEFAULT -1" + SubscriptionManager.GROUP_UUID + " TEXT" + ");"; + ");"; } } Loading tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +85 −11 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.telephony; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; 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; Loading @@ -27,6 +28,8 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import android.Manifest; import android.content.ContentValues; import android.content.Intent; import android.content.Intent; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.os.UserHandle; Loading @@ -49,6 +52,7 @@ public class SubscriptionControllerTest extends TelephonyTest { private String mCallingPackage; private String mCallingPackage; private SubscriptionController mSubscriptionControllerUT; private SubscriptionController mSubscriptionControllerUT; private MockContentResolver mMockContentResolver; private MockContentResolver mMockContentResolver; private FakeTelephonyProvider mFakeTelephonyProvider; @Mock @Mock private ITelephonyRegistry.Stub mTelephonyRegisteryMock; private ITelephonyRegistry.Stub mTelephonyRegisteryMock; Loading @@ -70,13 +74,15 @@ public class SubscriptionControllerTest extends TelephonyTest { mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone}); mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone}); mMockContentResolver = (MockContentResolver) mContext.getContentResolver(); mMockContentResolver = (MockContentResolver) mContext.getContentResolver(); mFakeTelephonyProvider = new FakeTelephonyProvider(); mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(), mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(), new FakeTelephonyProvider()); mFakeTelephonyProvider); } } @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); /* should clear fake content provider and resolver here */ /* should clear fake content provider and resolver here */ mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null); mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null); Loading Loading @@ -343,7 +349,7 @@ public class SubscriptionControllerTest extends TelephonyTest { .notifyOpportunisticSubscriptionInfoChanged(); .notifyOpportunisticSubscriptionInfoChanged(); testInsertSim(); testInsertSim(); testInsertSim2(); mSubscriptionControllerUT.addSubInfoRecord("test2", 0); // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions // should return empty list and no callback triggered. // should return empty list and no callback triggered. Loading Loading @@ -374,16 +380,84 @@ public class SubscriptionControllerTest extends TelephonyTest { .notifyOpportunisticSubscriptionInfoChanged(); .notifyOpportunisticSubscriptionInfoChanged(); } } private void testInsertSim2() { @Test // verify there's already a SIM profile added. @SmallTest assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage)); public void testSetSubscriptionGroupWithModifyPermission() throws Exception { testInsertSim(); int slotID = 0; mSubscriptionControllerUT.addSubInfoRecord("test2", 0); //insert one Subscription Info mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mSubscriptionControllerUT.addSubInfoRecord("test2", slotID); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); // With modify permission it should succeed. mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); // Calling it again should generate a new group ID. String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, 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); } //verify there is one sim @Test assertEquals(2, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage)); @SmallTest public void testSetSubscriptionGroupWithCarrierPrivilegePermission() 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(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); // With modify permission it should succeed. doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // Revoke carrier privilege of sub 2 but make it manageable by caller. doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2); doReturn(true).when(mSubscriptionManager).canManageSubscription( eq(subInfoList.get(1)), anyString()); String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, newGroupId); assertNotEquals(groupId, newGroupId); } } private void registerMockTelephonyRegistry() { private void registerMockTelephonyRegistry() { Loading Loading
src/java/com/android/internal/telephony/SubscriptionController.java +111 −41 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony; package com.android.internal.telephony; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; import android.Manifest; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.AppOpsManager; import android.app.AppOpsManager; Loading Loading @@ -63,6 +65,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Map.Entry; import java.util.Objects; import java.util.Objects; import java.util.Set; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Collectors; Loading Loading @@ -313,8 +316,8 @@ public class SubscriptionController extends ISub.Stub { } } boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow( boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow( SubscriptionManager.IS_OPPORTUNISTIC)) == 1; SubscriptionManager.IS_OPPORTUNISTIC)) == 1; int parentSubId = cursor.getInt(cursor.getColumnIndexOrThrow( String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow( SubscriptionManager.PARENT_SUB_ID)); SubscriptionManager.GROUP_UUID)); if (VDBG) { if (VDBG) { String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId); String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId); Loading @@ -325,7 +328,7 @@ public class SubscriptionController extends ISub.Stub { + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:" + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:" + isEmbedded + " accessRules:" + Arrays.toString(accessRules) + isEmbedded + " accessRules:" + Arrays.toString(accessRules) + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic + " cardId:" + cardIdToPrint + " isOpportunistic:" + isOpportunistic + " parentSubId:" + parentSubId); + " groupUUID:" + groupUUID); } } // If line1number has been set to a different number, use it instead. // If line1number has been set to a different number, use it instead. Loading @@ -335,7 +338,7 @@ public class SubscriptionController extends ISub.Stub { } } return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardId, isOpportunistic, parentSubId); isEmbedded, accessRules, cardId, isOpportunistic, groupUUID); } } /** /** Loading Loading @@ -2025,7 +2028,6 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.CB_OPT_OUT_DIALOG: case SubscriptionManager.CB_OPT_OUT_DIALOG: case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: case SubscriptionManager.ENHANCED_4G_MODE_ENABLED: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.PARENT_SUB_ID: case SubscriptionManager.VT_IMS_ENABLED: case SubscriptionManager.VT_IMS_ENABLED: case SubscriptionManager.WFC_IMS_ENABLED: case SubscriptionManager.WFC_IMS_ENABLED: case SubscriptionManager.WFC_IMS_MODE: case SubscriptionManager.WFC_IMS_MODE: Loading Loading @@ -2085,7 +2087,7 @@ public class SubscriptionController extends ISub.Stub { case SubscriptionManager.WFC_IMS_ROAMING_MODE: case SubscriptionManager.WFC_IMS_ROAMING_MODE: case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: case SubscriptionManager.WFC_IMS_ROAMING_ENABLED: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.IS_OPPORTUNISTIC: case SubscriptionManager.PARENT_SUB_ID: case SubscriptionManager.GROUP_UUID: resultValue = cursor.getInt(0) + ""; resultValue = cursor.getInt(0) + ""; break; break; default: default: Loading Loading @@ -2237,40 +2239,6 @@ public class SubscriptionController extends ISub.Stub { String.valueOf(opportunistic ? 1 : 0)); String.valueOf(opportunistic ? 1 : 0)); } } /** * Set parent subId (parentSubId) of another subscription (subId). * It's used in scenarios where a child ubscription is bundled with a * primary parent subscription. The child subscription will typically be opportunistic * and will be used to provide data services where available, with the parent being * the primary fallback subscription. * * @param parentSubId subId of its parent subscription. * @param subId the unique SubscriptionInfo index in database * @return the number of records updated */ @Override public int setParentSubId(int parentSubId, int subId) { enforceModifyPhoneState("setParentSubId"); final long token = Binder.clearCallingIdentity(); try { if (!SubscriptionManager.isUsableSubIdValue(parentSubId) || parentSubId > getAllSubInfoCount(mContext.getOpPackageName()) || parentSubId == subId) { if (DBG) { logd("[setParentSubId]- fail with parentSubId " + parentSubId + " subId " + subId); } return -1; } return setSubscriptionProperty(subId, SubscriptionManager.PARENT_SUB_ID, String.valueOf(parentSubId)); } finally { Binder.restoreCallingIdentity(token); } } @Override @Override public int setPreferredData(int subId) { public int setPreferredData(int subId) { enforceModifyPhoneState("setPreferredData"); enforceModifyPhoneState("setPreferredData"); Loading Loading @@ -2306,6 +2274,108 @@ public class SubscriptionController extends ISub.Stub { callingPackage, mCacheOpportunisticSubInfoList); callingPackage, mCacheOpportunisticSubInfoList); } } /** * Inform SubscriptionManager that subscriptions in the list are bundled * as a group. Typically it's a primary subscription and an opportunistic * subscription. It should only affect multi-SIM scenarios where primary * and opportunistic subscriptions can be activated together. * Being in the same group means they might be activated or deactivated * together, some of them may be invisible to the users, etc. * * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE} * permission or can manage all subscriptions in the list, according to their * access rules. * * @return groupUUID a UUID assigned to the subscription group. It returns * null if fails. * */ @Override public String setSubscriptionGroup(int[] subIdList, String callingPackage) { boolean hasModifyPermission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.MODIFY_PHONE_STATE) == PERMISSION_GRANTED; // If caller doesn't have modify permission or carrier privilege permission on certain // 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. Set<Integer> subIdCheckList = new HashSet<>(); for (int subId : subIdList) { if (!mTelephonyManager.hasCarrierPrivileges(subId)) { subIdCheckList.add(subId); } } long identity = Binder.clearCallingIdentity(); try { if (!isSubInfoReady()) { if (DBG) logdl("[getSubscriptionInfoList] Sub Controller not ready"); return null; } SubscriptionManager subscriptionManager = (SubscriptionManager) mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); List<SubscriptionInfo> subList = getSubInfo(null, null); for (SubscriptionInfo subInfo : subList) { if (subIdCheckList.contains(subInfo.getSubscriptionId())) { // If caller doesn't have modify permission or privilege access to // the subscription, operation is invalid and returns null. if (hasModifyPermission || (subInfo.isEmbedded() && subscriptionManager.canManageSubscription( subInfo, callingPackage))) { subIdCheckList.remove(subInfo.getSubscriptionId()); } else { if (DBG) { logdl("setSubscriptionGroup doesn't have permission on" + " subInfo " + subInfo); } return null; } } } if (!subIdCheckList.isEmpty()) { // Some SubId not found. StringBuilder subIdNotFound = new StringBuilder(); for (int subId : subIdCheckList) { subIdNotFound.append(subId + " "); } if (DBG) { logdl("setSubscriptionGroup subId not existed: " + subIdNotFound.toString()); } return null; } // Generate a UUID. String groupUUID = UUID.randomUUID().toString(); // Selection should be: "in (subId1, subId2, ...)". StringBuilder selection = new StringBuilder(); selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID); selection.append(" IN ("); 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); refreshCachedActiveSubscriptionInfoList(); return groupUUID; } finally { Binder.restoreCallingIdentity(identity); } } // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList. // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList. // They are doing similar things except operating on different cache. // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( Loading Loading @@ -2336,7 +2406,7 @@ public class SubscriptionController extends ISub.Stub { try { try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, subscriptionInfo.getSubscriptionId(), callingPackage, "getOpportunisticSubscriptions"); "getSubscriptionInfoList"); } catch (SecurityException e) { } catch (SecurityException e) { return false; return false; } } Loading
tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -97,7 +97,7 @@ public class FakeTelephonyProvider extends MockContentProvider { + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_MODE + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1," + SubscriptionManager.WFC_IMS_ROAMING_ENABLED + " INTEGER DEFAULT -1," + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0," + SubscriptionManager.IS_OPPORTUNISTIC + " INTEGER DEFAULT 0," + SubscriptionManager.PARENT_SUB_ID + " INTEGER DEFAULT -1" + SubscriptionManager.GROUP_UUID + " TEXT" + ");"; + ");"; } } Loading
tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +85 −11 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.internal.telephony; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; 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; Loading @@ -27,6 +28,8 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import android.Manifest; import android.content.ContentValues; import android.content.Intent; import android.content.Intent; import android.os.Bundle; import android.os.Bundle; import android.os.UserHandle; import android.os.UserHandle; Loading @@ -49,6 +52,7 @@ public class SubscriptionControllerTest extends TelephonyTest { private String mCallingPackage; private String mCallingPackage; private SubscriptionController mSubscriptionControllerUT; private SubscriptionController mSubscriptionControllerUT; private MockContentResolver mMockContentResolver; private MockContentResolver mMockContentResolver; private FakeTelephonyProvider mFakeTelephonyProvider; @Mock @Mock private ITelephonyRegistry.Stub mTelephonyRegisteryMock; private ITelephonyRegistry.Stub mTelephonyRegisteryMock; Loading @@ -70,13 +74,15 @@ public class SubscriptionControllerTest extends TelephonyTest { mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone}); mSubscriptionControllerUT.getInstance().updatePhonesAvailability(new Phone[]{mPhone}); mMockContentResolver = (MockContentResolver) mContext.getContentResolver(); mMockContentResolver = (MockContentResolver) mContext.getContentResolver(); mFakeTelephonyProvider = new FakeTelephonyProvider(); mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(), mMockContentResolver.addProvider(SubscriptionManager.CONTENT_URI.getAuthority(), new FakeTelephonyProvider()); mFakeTelephonyProvider); } } @After @After public void tearDown() throws Exception { public void tearDown() throws Exception { mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); /* should clear fake content provider and resolver here */ /* should clear fake content provider and resolver here */ mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null); mContext.getContentResolver().delete(SubscriptionManager.CONTENT_URI, null, null); Loading Loading @@ -343,7 +349,7 @@ public class SubscriptionControllerTest extends TelephonyTest { .notifyOpportunisticSubscriptionInfoChanged(); .notifyOpportunisticSubscriptionInfoChanged(); testInsertSim(); testInsertSim(); testInsertSim2(); mSubscriptionControllerUT.addSubInfoRecord("test2", 0); // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions // Neither sub1 or sub2 are opportunistic. So getOpportunisticSubscriptions // should return empty list and no callback triggered. // should return empty list and no callback triggered. Loading Loading @@ -374,16 +380,84 @@ public class SubscriptionControllerTest extends TelephonyTest { .notifyOpportunisticSubscriptionInfoChanged(); .notifyOpportunisticSubscriptionInfoChanged(); } } private void testInsertSim2() { @Test // verify there's already a SIM profile added. @SmallTest assertEquals(1, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage)); public void testSetSubscriptionGroupWithModifyPermission() throws Exception { testInsertSim(); int slotID = 0; mSubscriptionControllerUT.addSubInfoRecord("test2", 0); //insert one Subscription Info mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mSubscriptionControllerUT.addSubInfoRecord("test2", slotID); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); // With modify permission it should succeed. mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); // Calling it again should generate a new group ID. String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, 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); } //verify there is one sim @Test assertEquals(2, mSubscriptionControllerUT.getAllSubInfoCount(mCallingPackage)); @SmallTest public void testSetSubscriptionGroupWithCarrierPrivilegePermission() 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(); mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL); mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); int[] subIdList = new int[] {1, 2}; // It should fail since it has no permission. String groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); // With modify permission it should succeed. doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(1); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertEquals(null, groupId); doReturn(true).when(mTelephonyManager).hasCarrierPrivileges(2); groupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, groupId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // Revoke carrier privilege of sub 2 but make it manageable by caller. doReturn(false).when(mTelephonyManager).hasCarrierPrivileges(2); doReturn(true).when(mSubscriptionManager).canManageSubscription( eq(subInfoList.get(1)), anyString()); String newGroupId = mSubscriptionControllerUT.setSubscriptionGroup( subIdList, mContext.getOpPackageName()); assertNotEquals(null, newGroupId); assertNotEquals(groupId, newGroupId); } } private void registerMockTelephonyRegistry() { private void registerMockTelephonyRegistry() { Loading