Loading src/java/com/android/internal/telephony/GsmCdmaConnection.java +89 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Registrant; import android.os.SystemClock; import android.telephony.CarrierConfigManager; Loading Loading @@ -74,6 +75,8 @@ public class GsmCdmaConnection extends Connection { Handler mHandler; private PowerManager.WakeLock mPartialWakeLock; // The cached delay to be used between DTMF tones fetched from carrier config. private int mDtmfToneDelay = 0; Loading @@ -83,11 +86,13 @@ public class GsmCdmaConnection extends Connection { static final int EVENT_DTMF_DONE = 1; static final int EVENT_PAUSE_DONE = 2; static final int EVENT_NEXT_POST_DIAL = 3; static final int EVENT_WAKE_LOCK_TIMEOUT = 4; static final int EVENT_DTMF_DELAY_DONE = 5; //***** Constants static final int PAUSE_DELAY_MILLIS_GSM = 3 * 1000; static final int PAUSE_DELAY_MILLIS_CDMA = 2 * 1000; static final int WAKE_LOCK_TIMEOUT_MILLIS = 60 * 1000; //***** Inner Classes Loading @@ -104,6 +109,9 @@ public class GsmCdmaConnection extends Connection { case EVENT_PAUSE_DONE: processNextPostDialChar(); break; case EVENT_WAKE_LOCK_TIMEOUT: releaseWakeLock(); break; case EVENT_DTMF_DONE: // We may need to add a delay specified by carrier between DTMF tones that are // sent out. Loading @@ -119,6 +127,8 @@ public class GsmCdmaConnection extends Connection { /** This is probably an MT call that we first saw in a CLCC response or a hand over. */ public GsmCdmaConnection (GsmCdmaPhone phone, DriverCall dc, GsmCdmaCallTracker ct, int index) { super(phone.getPhoneType()); createWakeLock(phone.getContext()); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading Loading @@ -149,6 +159,8 @@ public class GsmCdmaConnection extends Connection { public GsmCdmaConnection (GsmCdmaPhone phone, String dialString, GsmCdmaCallTracker ct, GsmCdmaCall parent, boolean isEmergencyCall) { super(phone.getPhoneType()); createWakeLock(phone.getContext()); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading Loading @@ -203,6 +215,8 @@ public class GsmCdmaConnection extends Connection { public GsmCdmaConnection(Context context, CdmaCallWaitingNotification cw, GsmCdmaCallTracker ct, GsmCdmaCall parent) { super(parent.getPhone().getPhoneType()); createWakeLock(context); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading @@ -226,6 +240,7 @@ public class GsmCdmaConnection extends Connection { if (mParent != null) { mParent.detach(this); } releaseAllWakeLocks(); } static boolean equalsHandlesNulls(Object a, Object b) { Loading Loading @@ -618,6 +633,7 @@ public class GsmCdmaConnection extends Connection { mOrigConnection = null; } clearPostDialListeners(); releaseWakeLock(); return changed; } Loading @@ -633,6 +649,7 @@ public class GsmCdmaConnection extends Connection { mParent.detach(this); } } releaseWakeLock(); } // Returns true if state has changed, false if nothing changed Loading Loading @@ -774,7 +791,21 @@ public class GsmCdmaConnection extends Connection { if (!mIsIncoming) { // outgoing calls only processNextPostDialChar(); } else { // Only release wake lock for incoming calls, for outgoing calls the wake lock // will be released after any pause-dial is completed releaseWakeLock(); } } /** * We have completed the migration of another connection to this GsmCdmaConnection (for example, * in the case of SRVCC) and not still DIALING/ALERTING/INCOMING/WAITING. */ void onConnectedConnectionMigrated() { // We can release the wakelock in this case, the migrated call is not still // DIALING/ALERTING/INCOMING/WAITING. releaseWakeLock(); } private void Loading Loading @@ -861,7 +892,17 @@ public class GsmCdmaConnection extends Connection { @Override protected void finalize() { /** * It is understood that This finalizer is not guaranteed * to be called and the release lock call is here just in * case there is some path that doesn't call onDisconnect * and or onConnectedInOrOut. */ if (mPartialWakeLock != null && mPartialWakeLock.isHeld()) { Rlog.e(LOG_TAG, "UNEXPECTED; mPartialWakeLock is held when finalizing."); } clearPostDialListeners(); releaseWakeLock(); } private void Loading @@ -870,6 +911,7 @@ public class GsmCdmaConnection extends Connection { Registrant postDialHandler; if (mPostDialState == PostDialState.CANCELLED) { releaseWakeLock(); return; } Loading @@ -877,6 +919,9 @@ public class GsmCdmaConnection extends Connection { mPostDialString.length() <= mNextPostDialChar) { setPostDialState(PostDialState.COMPLETE); // We were holding a wake lock until pause-dial was complete, so give it up now releaseWakeLock(); // notifyMessage.arg1 is 0 on complete c = 0; } else { Loading Loading @@ -970,18 +1015,60 @@ public class GsmCdmaConnection extends Connection { * @param s new PostDialState */ private void setPostDialState(PostDialState s) { if (s == PostDialState.STARTED || s == PostDialState.PAUSE) { synchronized (mPartialWakeLock) { if (mPartialWakeLock.isHeld()) { mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); } else { acquireWakeLock(); } Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); } } else { mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); releaseWakeLock(); } mPostDialState = s; notifyPostDialListeners(); } @UnsupportedAppUsage private void createWakeLock(Context context) { // no-op PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); } @UnsupportedAppUsage private void acquireWakeLock() { // no-op if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { log("acquireWakeLock"); mPartialWakeLock.acquire(); } } } private void releaseWakeLock() { if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { if (mPartialWakeLock.isHeld()) { log("releaseWakeLock"); mPartialWakeLock.release(); } } } } private void releaseAllWakeLocks() { if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { while (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } } } } @UnsupportedAppUsage Loading src/java/com/android/internal/telephony/SubscriptionController.java +78 −21 Original line number Diff line number Diff line Loading @@ -3903,20 +3903,62 @@ public class SubscriptionController extends ISub.Stub { // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList) { synchronized (mSubInfoListLock) { // Filter the list to only include subscriptions which the caller can manage. return cacheSubList.stream() .filter(subscriptionInfo -> { boolean canReadPhoneState = false; boolean canReadIdentifiers = false; boolean canReadPhoneNumber = false; try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId, "getSubscriptionInfoList"); // If the calling package has the READ_PHONE_STATE permission then check if the caller // also has access to subscriber identifiers and the phone number 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, callingFeatureId, "getSubscriptionInfoList"); canReadPhoneNumber = hasPhoneNumberAccess( SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, callingFeatureId, "getSubscriptionInfoList"); } } catch (SecurityException e) { return 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. } }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, callingPackage, callingFeatureId, "getSubscriptionInfoList")) .collect(Collectors.toList()); synchronized (mSubInfoListLock) { // If the caller can read all phone state, just return the full list. if (canReadIdentifiers && canReadPhoneNumber) { return new ArrayList<>(cacheSubList); } // Filter the list to only include subscriptions which the caller can manage. List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size()); for (SubscriptionInfo subscriptionInfo : cacheSubList) { int subId = subscriptionInfo.getSubscriptionId(); boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId( mContext, 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, canReadPhoneNumber)); } } return subscriptions; } } Loading @@ -3937,8 +3979,24 @@ public class SubscriptionController extends ISub.Stub { callingFeatureId, message); boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId, message); if (!hasIdentifierAccess || !hasPhoneNumberAccess) { result = new SubscriptionInfo(subInfo); return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess); } /** * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the * potentially modified object. * * <p>If the caller specifies the package does not have identifier or phone number 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, boolean hasPhoneNumberAccess) { if (hasIdentifierAccess && hasPhoneNumberAccess) { return subInfo; } SubscriptionInfo result = new SubscriptionInfo(subInfo); if (!hasIdentifierAccess) { result.clearIccId(); result.clearCardString(); Loading @@ -3946,7 +4004,6 @@ public class SubscriptionController extends ISub.Stub { if (!hasPhoneNumberAccess) { result.clearNumber(); } } return result; } Loading tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +55 −3 Original line number Diff line number Diff line Loading @@ -1273,6 +1273,54 @@ public class SubscriptionControllerTest extends TelephonyTest { } } @Test public void testGetActiveSubscriptionInfoListWithCarrierPrivilegesOnOneSubId() throws Exception { // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with // the ICC ID and phone number. testInsertSim(); doReturn(2).when(mTelephonyManager).getPhoneCount(); mSubscriptionControllerUT.addSubInfoRecord("test2", 1); int firstSubId = getFirstSubId(); int secondSubId = getSubIdAtIndex(1); mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, secondSubId); setupIdentifierCarrierPrivilegesTest(); mContextFixture.removeCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setCarrierPrivilegesForSubId(false, firstSubId); setCarrierPrivilegesForSubId(true, secondSubId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test2", subInfo.getIccId()); assertEquals(DISPLAY_NUMBER, subInfo.getNumber()); } @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(); setupReadPhoneNumbersTest(); setIdentifierAccess(true); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test", subInfo.getIccId()); assertEquals(UNAVAILABLE_NUMBER, subInfo.getNumber()); } @Test public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier Loading Loading @@ -1403,9 +1451,13 @@ public class SubscriptionControllerTest extends TelephonyTest { } private int getFirstSubId() throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); assertTrue(subIds != null && subIds.length != 0); return subIds[0]; return getSubIdAtIndex(0); } private int getSubIdAtIndex(int index) throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibileOnly*/false); assertTrue(subIds != null && subIds.length > index); return subIds[index]; } @Test Loading tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -102,6 +102,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.permission.PermissionManagerService; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; Loading Loading @@ -839,6 +840,14 @@ public abstract class TelephonyTest { mTelephonyManager).getCarrierPrivilegeStatus(anyInt()); } protected void setCarrierPrivilegesForSubId(boolean hasCarrierPrivileges, int subId) { TelephonyManager mockTelephonyManager = Mockito.mock(TelephonyManager.class); doReturn(mockTelephonyManager).when(mTelephonyManager).createForSubscriptionId(subId); doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when( mockTelephonyManager).getCarrierPrivilegeStatus(anyInt()); } protected final void waitForHandlerAction(Handler h, long timeoutMillis) { final CountDownLatch lock = new CountDownLatch(1); h.post(lock::countDown); Loading Loading
src/java/com/android/internal/telephony/GsmCdmaConnection.java +89 −2 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Registrant; import android.os.SystemClock; import android.telephony.CarrierConfigManager; Loading Loading @@ -74,6 +75,8 @@ public class GsmCdmaConnection extends Connection { Handler mHandler; private PowerManager.WakeLock mPartialWakeLock; // The cached delay to be used between DTMF tones fetched from carrier config. private int mDtmfToneDelay = 0; Loading @@ -83,11 +86,13 @@ public class GsmCdmaConnection extends Connection { static final int EVENT_DTMF_DONE = 1; static final int EVENT_PAUSE_DONE = 2; static final int EVENT_NEXT_POST_DIAL = 3; static final int EVENT_WAKE_LOCK_TIMEOUT = 4; static final int EVENT_DTMF_DELAY_DONE = 5; //***** Constants static final int PAUSE_DELAY_MILLIS_GSM = 3 * 1000; static final int PAUSE_DELAY_MILLIS_CDMA = 2 * 1000; static final int WAKE_LOCK_TIMEOUT_MILLIS = 60 * 1000; //***** Inner Classes Loading @@ -104,6 +109,9 @@ public class GsmCdmaConnection extends Connection { case EVENT_PAUSE_DONE: processNextPostDialChar(); break; case EVENT_WAKE_LOCK_TIMEOUT: releaseWakeLock(); break; case EVENT_DTMF_DONE: // We may need to add a delay specified by carrier between DTMF tones that are // sent out. Loading @@ -119,6 +127,8 @@ public class GsmCdmaConnection extends Connection { /** This is probably an MT call that we first saw in a CLCC response or a hand over. */ public GsmCdmaConnection (GsmCdmaPhone phone, DriverCall dc, GsmCdmaCallTracker ct, int index) { super(phone.getPhoneType()); createWakeLock(phone.getContext()); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading Loading @@ -149,6 +159,8 @@ public class GsmCdmaConnection extends Connection { public GsmCdmaConnection (GsmCdmaPhone phone, String dialString, GsmCdmaCallTracker ct, GsmCdmaCall parent, boolean isEmergencyCall) { super(phone.getPhoneType()); createWakeLock(phone.getContext()); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading Loading @@ -203,6 +215,8 @@ public class GsmCdmaConnection extends Connection { public GsmCdmaConnection(Context context, CdmaCallWaitingNotification cw, GsmCdmaCallTracker ct, GsmCdmaCall parent) { super(parent.getPhone().getPhoneType()); createWakeLock(context); acquireWakeLock(); mOwner = ct; mHandler = new MyHandler(mOwner.getLooper()); Loading @@ -226,6 +240,7 @@ public class GsmCdmaConnection extends Connection { if (mParent != null) { mParent.detach(this); } releaseAllWakeLocks(); } static boolean equalsHandlesNulls(Object a, Object b) { Loading Loading @@ -618,6 +633,7 @@ public class GsmCdmaConnection extends Connection { mOrigConnection = null; } clearPostDialListeners(); releaseWakeLock(); return changed; } Loading @@ -633,6 +649,7 @@ public class GsmCdmaConnection extends Connection { mParent.detach(this); } } releaseWakeLock(); } // Returns true if state has changed, false if nothing changed Loading Loading @@ -774,7 +791,21 @@ public class GsmCdmaConnection extends Connection { if (!mIsIncoming) { // outgoing calls only processNextPostDialChar(); } else { // Only release wake lock for incoming calls, for outgoing calls the wake lock // will be released after any pause-dial is completed releaseWakeLock(); } } /** * We have completed the migration of another connection to this GsmCdmaConnection (for example, * in the case of SRVCC) and not still DIALING/ALERTING/INCOMING/WAITING. */ void onConnectedConnectionMigrated() { // We can release the wakelock in this case, the migrated call is not still // DIALING/ALERTING/INCOMING/WAITING. releaseWakeLock(); } private void Loading Loading @@ -861,7 +892,17 @@ public class GsmCdmaConnection extends Connection { @Override protected void finalize() { /** * It is understood that This finalizer is not guaranteed * to be called and the release lock call is here just in * case there is some path that doesn't call onDisconnect * and or onConnectedInOrOut. */ if (mPartialWakeLock != null && mPartialWakeLock.isHeld()) { Rlog.e(LOG_TAG, "UNEXPECTED; mPartialWakeLock is held when finalizing."); } clearPostDialListeners(); releaseWakeLock(); } private void Loading @@ -870,6 +911,7 @@ public class GsmCdmaConnection extends Connection { Registrant postDialHandler; if (mPostDialState == PostDialState.CANCELLED) { releaseWakeLock(); return; } Loading @@ -877,6 +919,9 @@ public class GsmCdmaConnection extends Connection { mPostDialString.length() <= mNextPostDialChar) { setPostDialState(PostDialState.COMPLETE); // We were holding a wake lock until pause-dial was complete, so give it up now releaseWakeLock(); // notifyMessage.arg1 is 0 on complete c = 0; } else { Loading Loading @@ -970,18 +1015,60 @@ public class GsmCdmaConnection extends Connection { * @param s new PostDialState */ private void setPostDialState(PostDialState s) { if (s == PostDialState.STARTED || s == PostDialState.PAUSE) { synchronized (mPartialWakeLock) { if (mPartialWakeLock.isHeld()) { mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); } else { acquireWakeLock(); } Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); } } else { mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); releaseWakeLock(); } mPostDialState = s; notifyPostDialListeners(); } @UnsupportedAppUsage private void createWakeLock(Context context) { // no-op PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); } @UnsupportedAppUsage private void acquireWakeLock() { // no-op if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { log("acquireWakeLock"); mPartialWakeLock.acquire(); } } } private void releaseWakeLock() { if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { if (mPartialWakeLock.isHeld()) { log("releaseWakeLock"); mPartialWakeLock.release(); } } } } private void releaseAllWakeLocks() { if (mPartialWakeLock != null) { synchronized (mPartialWakeLock) { while (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } } } } @UnsupportedAppUsage Loading
src/java/com/android/internal/telephony/SubscriptionController.java +78 −21 Original line number Diff line number Diff line Loading @@ -3903,20 +3903,62 @@ public class SubscriptionController extends ISub.Stub { // They are doing similar things except operating on different cache. private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper( String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList) { synchronized (mSubInfoListLock) { // Filter the list to only include subscriptions which the caller can manage. return cacheSubList.stream() .filter(subscriptionInfo -> { boolean canReadPhoneState = false; boolean canReadIdentifiers = false; boolean canReadPhoneNumber = false; try { return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subscriptionInfo.getSubscriptionId(), callingPackage, canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, callingFeatureId, "getSubscriptionInfoList"); // If the calling package has the READ_PHONE_STATE permission then check if the caller // also has access to subscriber identifiers and the phone number 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, callingFeatureId, "getSubscriptionInfoList"); canReadPhoneNumber = hasPhoneNumberAccess( SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, callingFeatureId, "getSubscriptionInfoList"); } } catch (SecurityException e) { return 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. } }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo, callingPackage, callingFeatureId, "getSubscriptionInfoList")) .collect(Collectors.toList()); synchronized (mSubInfoListLock) { // If the caller can read all phone state, just return the full list. if (canReadIdentifiers && canReadPhoneNumber) { return new ArrayList<>(cacheSubList); } // Filter the list to only include subscriptions which the caller can manage. List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size()); for (SubscriptionInfo subscriptionInfo : cacheSubList) { int subId = subscriptionInfo.getSubscriptionId(); boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId( mContext, 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, canReadPhoneNumber)); } } return subscriptions; } } Loading @@ -3937,8 +3979,24 @@ public class SubscriptionController extends ISub.Stub { callingFeatureId, message); boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId, message); if (!hasIdentifierAccess || !hasPhoneNumberAccess) { result = new SubscriptionInfo(subInfo); return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess); } /** * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the * potentially modified object. * * <p>If the caller specifies the package does not have identifier or phone number 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, boolean hasPhoneNumberAccess) { if (hasIdentifierAccess && hasPhoneNumberAccess) { return subInfo; } SubscriptionInfo result = new SubscriptionInfo(subInfo); if (!hasIdentifierAccess) { result.clearIccId(); result.clearCardString(); Loading @@ -3946,7 +4004,6 @@ public class SubscriptionController extends ISub.Stub { if (!hasPhoneNumberAccess) { result.clearNumber(); } } return result; } Loading
tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java +55 −3 Original line number Diff line number Diff line Loading @@ -1273,6 +1273,54 @@ public class SubscriptionControllerTest extends TelephonyTest { } } @Test public void testGetActiveSubscriptionInfoListWithCarrierPrivilegesOnOneSubId() throws Exception { // If an app does not have the READ_PHONE_STATE permission but has carrier privileges on one // out of multiple sub IDs then the SubscriptionInfo for that subId should be returned with // the ICC ID and phone number. testInsertSim(); doReturn(2).when(mTelephonyManager).getPhoneCount(); mSubscriptionControllerUT.addSubInfoRecord("test2", 1); int firstSubId = getFirstSubId(); int secondSubId = getSubIdAtIndex(1); mSubscriptionControllerUT.setDisplayNumber(DISPLAY_NUMBER, secondSubId); setupIdentifierCarrierPrivilegesTest(); mContextFixture.removeCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE); setCarrierPrivilegesForSubId(false, firstSubId); setCarrierPrivilegesForSubId(true, secondSubId); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test2", subInfo.getIccId()); assertEquals(DISPLAY_NUMBER, subInfo.getNumber()); } @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(); setupReadPhoneNumbersTest(); setIdentifierAccess(true); List<SubscriptionInfo> subInfoList = mSubscriptionControllerUT.getActiveSubscriptionInfoList(mCallingPackage, mCallingFeature); assertEquals(1, subInfoList.size()); SubscriptionInfo subInfo = subInfoList.get(0); assertEquals("test", subInfo.getIccId()); assertEquals(UNAVAILABLE_NUMBER, subInfo.getNumber()); } @Test public void testGetActiveSubscriptionInfoListWithPrivilegedPermission() throws Exception { // If the calling package has the READ_PRIVILEGED_PHONE_STATE permission or carrier Loading Loading @@ -1403,9 +1451,13 @@ public class SubscriptionControllerTest extends TelephonyTest { } private int getFirstSubId() throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibleOnly*/false); assertTrue(subIds != null && subIds.length != 0); return subIds[0]; return getSubIdAtIndex(0); } private int getSubIdAtIndex(int index) throws Exception { int[] subIds = mSubscriptionControllerUT.getActiveSubIdList(/*visibileOnly*/false); assertTrue(subIds != null && subIds.length > index); return subIds[index]; } @Test Loading
tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java +9 −0 Original line number Diff line number Diff line Loading @@ -102,6 +102,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.permission.PermissionManagerService; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; Loading Loading @@ -839,6 +840,14 @@ public abstract class TelephonyTest { mTelephonyManager).getCarrierPrivilegeStatus(anyInt()); } protected void setCarrierPrivilegesForSubId(boolean hasCarrierPrivileges, int subId) { TelephonyManager mockTelephonyManager = Mockito.mock(TelephonyManager.class); doReturn(mockTelephonyManager).when(mTelephonyManager).createForSubscriptionId(subId); doReturn(hasCarrierPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS : TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS).when( mockTelephonyManager).getCarrierPrivilegeStatus(anyInt()); } protected final void waitForHandlerAction(Handler h, long timeoutMillis) { final CountDownLatch lock = new CountDownLatch(1); h.post(lock::countDown); Loading