Enforce READ_PHONE_STATE for APIs involving call state
For API version 31+, ensure that READ_PHONE_STATE is checked on
APIs that retrieve/notify the call state of the device.
Bug: 157233955
Test: atest CtsTelecomTestCases2 CtsTelephony2TestCases
Change-Id: I9f8674a3075d3e0f75ee4f41eefce328c0fa6b91
diff --git a/core/api/current.txt b/core/api/current.txt
index 935cf70..aaa4c2d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40190,6 +40190,7 @@
field public static final int DURATION_MEDIUM = 2; // 0x2
field public static final int DURATION_SHORT = 1; // 0x1
field public static final int DURATION_VERY_SHORT = 0; // 0x0
+ field public static final long ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION = 157233955L; // 0x95f3323L
field public static final String EXTRA_CALL_BACK_NUMBER = "android.telecom.extra.CALL_BACK_NUMBER";
field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telecom.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_DISCONNECT_MESSAGE = "android.telecom.extra.CALL_DISCONNECT_MESSAGE";
@@ -41796,7 +41797,7 @@
method @Deprecated public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallDisconnectCauseChanged(int, int);
method @Deprecated public void onCallForwardingIndicatorChanged(boolean);
- method @Deprecated public void onCallStateChanged(int, String);
+ method @Deprecated @RequiresPermission(value=android.Manifest.permission.READ_PHONE_STATE, conditional=true) public void onCallStateChanged(int, String);
method @Deprecated public void onCellInfoChanged(java.util.List<android.telephony.CellInfo>);
method @Deprecated public void onCellLocationChanged(android.telephony.CellLocation);
method @Deprecated public void onDataActivity(int);
@@ -42342,7 +42343,7 @@
}
public static interface TelephonyCallback.CallStateListener {
- method public void onCallStateChanged(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void onCallStateChanged(int);
}
public static interface TelephonyCallback.CarrierNetworkListener {
@@ -42432,7 +42433,8 @@
method public int getActiveModemCount();
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCallComposerStatus();
- method public int getCallState();
+ method @Deprecated @RequiresPermission(value=android.Manifest.permission.READ_PHONE_STATE, conditional=true) public int getCallState();
+ method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getCallStateForSubscription();
method public int getCardIdForDefaultEuicc();
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
method public int getCarrierIdFromSimMccMnc();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e8bdd41..8987802 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10880,7 +10880,7 @@
method public java.util.List<android.telecom.PhoneAccount> getAllPhoneAccounts();
method public int getAllPhoneAccountsCount();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(boolean);
- method public int getCallState();
+ method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}, conditional=true) public int getCallState();
method public android.telecom.PhoneAccountHandle getConnectionManager();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCurrentTtyMode();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultDialerPackage(@NonNull android.os.UserHandle);
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 1da7dc4..49065aa 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -703,6 +703,10 @@
* calling {@link TelephonyManager#getCallState()} from within this callback may return a
* different state than the callback reports.
*
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
+ * targeting API level 31+.
+ *
* @param state call state
* @param phoneNumber call phone number. If application does not have
* {@link android.Manifest.permission#READ_CALL_LOG READ_CALL_LOG} permission or carrier
@@ -712,6 +716,7 @@
* @deprecated Use {@link TelephonyCallback.CallStateListener} instead.
*/
@Deprecated
+ @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
public void onCallStateChanged(@CallState int state, String phoneNumber) {
// default implementation empty
}
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 18949cd..1ab6e0f 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -752,6 +752,7 @@
*
* @param state the current call state
*/
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public void onCallStateChanged(@Annotation.CallState int state);
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3ea4458..b42a16d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -44,6 +44,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telecom.TelecomManager;
import android.telephony.Annotation;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
@@ -215,6 +216,18 @@
return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled(
TelephonyCallback.PHONE_STATE_LISTENER_LIMIT_CHANGE_ID, uid));
}
+
+ /**
+ * See {@link TelecomManager#ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION} for more
+ * information.
+ * @noinspection ConstantConditions
+ */
+ public boolean isCallStateReadPhoneStateEnforcedInPlatformCompat(String packageName,
+ UserHandle userHandle) {
+ return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled(
+ TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, packageName,
+ userHandle));
+ }
}
private final Context mContext;
@@ -2947,6 +2960,19 @@
}
}
+ // Only check READ_PHONE_STATE for CALL_STATE_CHANGED for API 31+.
+ if (mConfigurationProvider.isCallStateReadPhoneStateEnforcedInPlatformCompat(callingPackage,
+ Binder.getCallingUserHandle())) {
+ if (events.contains(TelephonyCallback.EVENT_LEGACY_CALL_STATE_CHANGED)
+ || events.contains(TelephonyCallback.EVENT_CALL_STATE_CHANGED)) {
+ if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
+ mContext, subId, callingPackage, callingFeatureId, message)) {
+ throw new SecurityException("CALL_STATE_CHANGED event requires "
+ + "READ_PHONE_STATE");
+ }
+ }
+ }
+
if (isPrecisePhoneStatePermissionRequired(events)) {
// check if calling app has either permission READ_PRECISE_PHONE_STATE
// or with carrier privileges
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 1677c8c..4886789 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -25,6 +25,8 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1004,6 +1006,17 @@
PRESENTATION_PAYPHONE})
public @interface Presentation {}
+
+ /**
+ * Enable READ_PHONE_STATE protection on APIs querying and notifying call state, such as
+ * {@code TelecomManager#getCallState}, {@link TelephonyManager#getCallStateForSubscription()},
+ * and {@link android.telephony.TelephonyCallback.CallStateListener}.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ // this magic number is a bug ID
+ public static final long ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION = 157233955L;
+
private static final String TAG = "TelecomManager";
@@ -1758,21 +1771,23 @@
* {@link TelephonyManager#CALL_STATE_OFFHOOK}
* {@link TelephonyManager#CALL_STATE_IDLE}
*
- * Note that this API does not require the
- * {@link android.Manifest.permission#READ_PHONE_STATE} permission. This is intentional, to
- * preserve the behavior of {@link TelephonyManager#getCallState()}, which also did not require
- * the permission.
- *
* Takes into consideration both managed and self-managed calls.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
+ * targeting API level 31+.
*
* @hide
*/
+ @RequiresPermission(anyOf = {READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PHONE_STATE}, conditional = true)
@SystemApi
public @CallState int getCallState() {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getCallState();
+ return service.getCallStateUsingPackage(mContext.getPackageName(),
+ mContext.getAttributionTag());
} catch (RemoteException e) {
Log.d(TAG, "RemoteException calling getCallState().", e);
}
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 78283fa..18afde7 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -195,11 +195,18 @@
/**
* @see TelecomServiceImpl#getCallState
+ * Note: only kept around to not break app compat, however this will throw a SecurityException
+ * on API 31+.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
int getCallState();
/**
+ * @see TelecomServiceImpl#getCallState
+ */
+ int getCallStateUsingPackage(String callingPackage, String callingFeatureId);
+
+ /**
* @see TelecomServiceImpl#endCall
*/
boolean endCall(String callingPackage);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d2da51a..eed42d9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5702,9 +5702,20 @@
* Note: The call state returned via this method may differ from what is reported by
* {@link PhoneStateListener#onCallStateChanged(int, String)}, as that callback only considers
* Telephony (mobile) calls.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
+ * targeting API level 31+.
*
* @return the current call state.
+ * @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
+ * specific telephony subscription (which allows carrier privileged apps),
+ * {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
+ * {@link TelecomManager#isInCall()}, which supplies an aggregate "in call" state for the entire
+ * device.
*/
+ @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
+ @Deprecated
public @CallState int getCallState() {
if (mContext != null) {
TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
@@ -5716,19 +5727,48 @@
}
/**
+ * Retrieve the call state for a specific subscription that was specified when this
+ * TelephonyManager instance was created.
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or that the calling
+ * application has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * @see TelephonyManager#createForSubscriptionId(int)
+ * @see TelephonyManager#createForPhoneAccountHandle(PhoneAccountHandle)
+ * @return The call state of the subscription associated with this TelephonyManager instance.
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ public @CallState int getCallStateForSubscription() {
+ return getCallState(getSubId());
+ }
+
+ /**
* Returns the Telephony call state for calls on a specific subscription.
* <p>
* Note: This method considers ONLY telephony/mobile calls, where {@link #getCallState()}
* considers the state of calls from other {@link android.telecom.ConnectionService}
* implementations.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
+ * targeting API level 31+ or that the calling application has carrier privileges
+ * (see {@link #hasCarrierPrivileges()}).
*
* @param subId the subscription to check call state for.
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
public @CallState int getCallState(int subId) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
- return getCallStateForSlot(phoneId);
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ return CALL_STATE_IDLE;
+ }
+ try {
+ return telephony.getCallStateForSubscription(subId, mContext.getPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException e) {
+ return CALL_STATE_IDLE;
+ }
}
/**
@@ -5745,22 +5785,28 @@
* Note: This method considers ONLY telephony/mobile calls, where {@link #getCallState()}
* considers the state of calls from other {@link android.telecom.ConnectionService}
* implementations.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} for applications
+ * targeting API level 31+ or that the calling application has carrier privileges
+ * (see {@link #hasCarrierPrivileges()}).
*
* @param slotIndex the SIM slot index to check call state for.
* @hide
*/
+ @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
public @CallState int getCallStateForSlot(int slotIndex) {
try {
+ int[] subId = SubscriptionManager.getSubId(slotIndex);
ITelephony telephony = getITelephony();
- if (telephony == null)
+ if (telephony == null || subId == null || subId.length == 0) {
return CALL_STATE_IDLE;
- return telephony.getCallStateForSlot(slotIndex);
- } catch (RemoteException ex) {
+ }
+ return telephony.getCallStateForSubscription(subId[0], mContext.getPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException | NullPointerException ex) {
// the phone process is restarting.
return CALL_STATE_IDLE;
- } catch (NullPointerException ex) {
- // the phone process is restarting.
- return CALL_STATE_IDLE;
}
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 46752b7..afc538d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -298,9 +298,9 @@
int getCallState();
/**
- * Returns the call state for a slot.
+ * Returns the call state for a specific subscriiption.
*/
- int getCallStateForSlot(int slotIndex);
+ int getCallStateForSubscription(int subId, String callingPackage, String featureId);
/**
* Replaced by getDataActivityForSubId.