Protect Device Identifiers behind priv permission and DO/PO checks
Bug: 110099294
Test: cts-tradefed run cts -m CtsDevicePolicyManagerTestCases \
-t com.android.cts.devicepolicy.DeviceOwnerTest.testDeviceOwnerCanGetDeviceIdentifiers
Test: cts-tradefed run cts -m CtsDevicePolicyManagerTestCases \
-t com.android.cts.devicepolicy.ManagedProfileTest#testGetDeviceIdentifiers
Test: cts-tradefed run cts -m CtsTelephonyTestCases -t android.telephony.cts.TelephonyManagerTest
Test: cts-tradefed run cts -m CtsPermissionTestCases -t android.permission.cts.TelephonyManagerPermissionTest
Change-Id: I3c82c53ec89cd17b34a61166ccc9e9747388efac
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 09ab671..f8a5f93 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5703,6 +5703,37 @@
}
/**
+ * Returns whether the specified package can read the device identifiers.
+ *
+ * @param packageName The package name of the app to check for device identifier access.
+ * @return whether the package can read the device identifiers.
+ *
+ * @hide
+ */
+ public boolean checkDeviceIdentifierAccess(String packageName) {
+ return checkDeviceIdentifierAccessAsUser(packageName, myUserId());
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_USERS, conditional = true)
+ public boolean checkDeviceIdentifierAccessAsUser(String packageName, int userId) {
+ throwIfParentInstance("checkDeviceIdentifierAccessAsUser");
+ if (packageName == null) {
+ return false;
+ }
+ if (mService != null) {
+ try {
+ return mService.checkDeviceIdentifierAccess(packageName, userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by a profile owner or device owner to set a default activity that the system selects
* to handle intents that match the given {@link IntentFilter}. This activity will remain the
* default intent handler even if the set of potential event handlers for the intent filter
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 35ea250..5e45450 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -153,6 +153,8 @@
void clearProfileOwner(in ComponentName who);
boolean hasUserSetupCompleted();
+ boolean checkDeviceIdentifierAccess(in String packageName, int userHandle);
+
void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo);
CharSequence getDeviceOwnerLockScreenInfo();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 8681893..26c2033 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -18,8 +18,10 @@
import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityThread;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
@@ -127,14 +129,21 @@
* <a href="/training/articles/security-key-attestation.html">key attestation</a> to obtain
* proof of the device's original identifiers.
*
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. Profile owner access is deprecated and will be removed in a future
+ * release.
+ *
* @return The serial number if specified.
*/
- @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public static String getSerial() {
IDeviceIdentifiersPolicyService service = IDeviceIdentifiersPolicyService.Stub
.asInterface(ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE));
try {
- return service.getSerial();
+ Application application = ActivityThread.currentApplication();
+ String callingPackage = application != null ? application.getPackageName() : null;
+ return service.getSerialForPackage(callingPackage);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/IDeviceIdentifiersPolicyService.aidl b/core/java/android/os/IDeviceIdentifiersPolicyService.aidl
index ac19f2b..87d358f 100644
--- a/core/java/android/os/IDeviceIdentifiersPolicyService.aidl
+++ b/core/java/android/os/IDeviceIdentifiersPolicyService.aidl
@@ -21,4 +21,5 @@
*/
interface IDeviceIdentifiersPolicyService {
String getSerial();
+ String getSerialForPackage(in String callingPackage);
}
\ No newline at end of file
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 4a58f88..2989df8 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -16,6 +16,7 @@
package com.android.bandwidthtest;
+import android.app.UiAutomation;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo.State;
@@ -74,7 +75,13 @@
Log.v(LOG_TAG, "Initialized mConnectionUtil");
mUid = Process.myUid();
mTManager = (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
- mDeviceId = mTManager.getDeviceId();
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ mDeviceId = mTManager.getDeviceId();
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
}
@Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 114c228..4b35046 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -322,6 +322,7 @@
<permission name="android.permission.PACKAGE_USAGE_STATS" />
<permission name="android.permission.READ_FRAME_BUFFER"/>
<permission name="android.permission.READ_LOWPAN_CREDENTIAL"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.REGISTER_CALL_PROVIDER"/>
<permission name="android.permission.REGISTER_CONNECTION_MANAGER"/>
diff --git a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
index 5f08257..3ffc5c5 100644
--- a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
+++ b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
@@ -16,18 +16,15 @@
package com.android.server.os;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Binder;
import android.os.Build;
import android.os.IDeviceIdentifiersPolicyService;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserHandle;
+
+import com.android.internal.telephony.TelephonyPermissions;
import com.android.server.SystemService;
/**
@@ -54,15 +51,22 @@
@Override
public @Nullable String getSerial() throws RemoteException {
- if (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID
- && mContext.checkCallingOrSelfPermission(
- Manifest.permission.READ_PHONE_STATE)
- != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(
- Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("getSerial requires READ_PHONE_STATE"
- + " or READ_PRIVILEGED_PHONE_STATE permission");
+ // Since this invocation is on the server side a null value is used for the
+ // callingPackage as the server's package name (typically android) should not be used
+ // for any device / profile owner checks. The majority of requests for the serial number
+ // should use the getSerialForPackage method with the calling package specified.
+ if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
+ /* callingPackage */ null, "getSerial")) {
+ return null;
+ }
+ return SystemProperties.get("ro.serialno", Build.UNKNOWN);
+ }
+
+ @Override
+ public @Nullable String getSerialForPackage(String callingPackage) throws RemoteException {
+ if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
+ callingPackage, "getSerial")) {
+ return null;
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 66cf48c..4350596 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -66,4 +66,9 @@
public long forceSecurityLogs() {
return 0;
}
+
+ @Override
+ public boolean checkDeviceIdentifierAccess(String packageName, int userHandle) {
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index eeb4ad3..913b844 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7861,6 +7861,21 @@
return getApplicationLabel(profileOwner.getPackageName(), userHandle);
}
+ @Override
+ public boolean checkDeviceIdentifierAccess(String packageName, int userHandle) {
+ // Allow access to the device owner.
+ ComponentName deviceOwner = getDeviceOwnerComponent(true);
+ if (deviceOwner != null && deviceOwner.getPackageName().equals(packageName)) {
+ return true;
+ }
+ // Allow access to the profile owner for the specified user.
+ ComponentName profileOwner = getProfileOwnerAsUser(userHandle);
+ if (profileOwner != null && profileOwner.getPackageName().equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Canonical name for a given package.
*/
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 80b6ead..4eacda3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1268,15 +1268,18 @@
* Returns the unique device ID, for example, the IMEI for GSM and the MEID
* or ESN for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*
* @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId() {
try {
ITelephony telephony = getITelephony();
@@ -1294,8 +1297,11 @@
* Returns the unique device ID of a subscription, for example, the IMEI for
* GSM and the MEID for CDMA phones. Return null if device ID is not available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*
* @param slotIndex of which deviceID is returned
*
@@ -1303,8 +1309,8 @@
* MEID for CDMA.
*/
@Deprecated
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId(int slotIndex) {
// FIXME this assumes phoneId == slotIndex
try {
@@ -1323,11 +1329,14 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei() {
return getImei(getSlotIndex());
}
@@ -1336,13 +1345,16 @@
* Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not
* available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*
* @param slotIndex of which IMEI is returned
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -1386,11 +1398,14 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid() {
return getMeid(getSlotIndex());
}
@@ -1398,13 +1413,16 @@
/**
* Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the
+ * device or profile owner. The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*
* @param slotIndex of which MEID is returned
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SuppressAutoDoc // No support for device / profile owner.
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getMeid(int slotIndex) {
ITelephony telephony = getITelephony();
if (telephony == null) return null;
@@ -2888,11 +2906,15 @@
* Returns the serial number of the SIM, if applicable. Return null if it is
* unavailable.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner, or that the calling app has carrier privileges (see {@link
+ * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSimSerialNumber() {
return getSimSerialNumber(getSubId());
}
@@ -2900,11 +2922,18 @@
/**
* Returns the serial number for the given subscription, if applicable. Return null if it is
* unavailable.
- * <p>
+ *
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner, or that the calling app has carrier privileges (see {@link
+ * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
+ *
* @param subId for which Sim Serial number is returned
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@UnsupportedAppUsage
public String getSimSerialNumber(int subId) {
try {
@@ -3039,11 +3068,15 @@
* Returns the unique subscriber ID, for example, the IMSI for a GSM phone.
* Return null if it is unavailable.
*
- * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner, or that the calling app has carrier privileges (see {@link
+ * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getSubscriberId() {
return getSubscriberId(getSubId());
}
@@ -3053,10 +3086,17 @@
* for a subscription.
* Return null if it is unavailable.
*
+ * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or
+ * profile owner, or that the calling app has carrier privileges (see {@link
+ * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the
+ * device; for more details see <a href="https://developer.android.com/work/managed-profiles">
+ * Work profiles</a>. Profile owner access is deprecated and will be removed in a future
+ * release.
+ *
* @param subId whose subscriber id is returned
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@UnsupportedAppUsage
public String getSubscriberId(int subId) {
try {
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 23ea237..dac7e04b 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -19,11 +19,16 @@
import android.Manifest;
import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -135,6 +140,169 @@
}
/**
+ * Check whether the caller (or self, if not processing an IPC) can read device identifiers.
+ *
+ * <p>This method behaves in one of the following ways:
+ * <ul>
+ * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission or the
+ * calling package passes a DevicePolicyManager Device Owner / Profile Owner device
+ * identifier access check,
+ * <li>throw SecurityException: if the caller does not meet any of the requirements and is
+ * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission.
+ * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+ * permission. In this case the caller would expect to have access to the device
+ * identifiers so false is returned instead of throwing a SecurityException to indicate
+ * the calling function should return dummy data.
+ * </ul>
+ */
+ public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context,
+ String callingPackage, String message) {
+ return checkCallingOrSelfReadDeviceIdentifiers(context,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message);
+ }
+
+ /**
+ * Check whether the caller (or self, if not processing an IPC) can read device identifiers.
+ *
+ * <p>This method behaves in one of the following ways:
+ * <ul>
+ * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission or the
+ * calling package passes a DevicePolicyManager Device Owner / Profile Owner device
+ * identifier access check,
+ * <li>throw SecurityException: if the caller does not meet any of the requirements and is
+ * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission
+ * or carrier privileges.
+ * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+ * permission or carrier privileges. In this case the caller would expect to have access
+ * to the device identifiers so false is returned instead of throwing a SecurityException
+ * to indicate the calling function should return dummy data.
+ * </ul>
+ */
+ public static boolean checkCallingOrSelfReadDeviceIdentifiers(Context context, int subId,
+ String callingPackage, String message) {
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ // if the device identifier check completes successfully then grant access.
+ if (checkReadDeviceIdentifiers(context, pid, uid, callingPackage)) {
+ return true;
+ }
+ // else the calling package is not authorized to access the device identifiers; call
+ // a central method to report the failure based on the target SDK and if the calling package
+ // has the READ_PHONE_STATE permission or carrier privileges that were previously required
+ // to access the identifiers.
+ return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
+ message);
+ }
+
+ /**
+ * Check whether the caller (or self, if not processing an IPC) can read subscriber identifiers.
+ *
+ * <p>This method behaves in one of the following ways:
+ * <ul>
+ * <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the calling
+ * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier
+ * access check, or the calling package has carrier privleges.
+ * <li>throw SecurityException: if the caller does not meet any of the requirements and is
+ * targeting Q or is targeting pre-Q and does not have the READ_PHONE_STATE permission.
+ * <li>return false: if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+ * permission. In this case the caller would expect to have access to the device
+ * identifiers so false is returned instead of throwing a SecurityException to indicate
+ * the calling function should return dummy data.
+ * </ul>
+ */
+ public static boolean checkCallingOrSelfReadSubscriberIdentifiers(Context context, int subId,
+ String callingPackage, String message) {
+ int pid = Binder.getCallingPid();
+ int uid = Binder.getCallingUid();
+ // if the device identifiers can be read then grant access to the subscriber identifiers
+ if (checkReadDeviceIdentifiers(context, pid, uid, callingPackage)) {
+ return true;
+ }
+ // If the calling package has carrier privileges then allow access to the subscriber
+ // identifiers.
+ if (SubscriptionManager.isValidSubscriptionId(subId) && getCarrierPrivilegeStatus(
+ TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return true;
+ }
+ return reportAccessDeniedToReadIdentifiers(context, subId, pid, uid, callingPackage,
+ message);
+ }
+
+ /**
+ * Checks whether the app with the given pid/uid can read device identifiers.
+ *
+ * @returns true if the caller has the READ_PRIVILEGED_PHONE_STATE permission or the calling
+ * package passes a DevicePolicyManager Device Owner / Profile Owner device identifier access
+ * check.
+ */
+ private static boolean checkReadDeviceIdentifiers(Context context, int pid, int uid,
+ String callingPackage) {
+ // Allow system and root access to the device identifiers.
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID) {
+ return true;
+ }
+ // Allow access to packages that have the READ_PRIVILEGED_PHONE_STATE permission.
+ if (context.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid,
+ uid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ // if the calling package is null then return now as there's no way to perform the
+ // DevicePolicyManager device / profile owner checks.
+ if (callingPackage == null) {
+ return false;
+ }
+ // Allow access to a device / profile owner app.
+ DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ if (devicePolicyManager != null && devicePolicyManager.checkDeviceIdentifierAccessAsUser(
+ callingPackage, Binder.getCallingUserHandle().getIdentifier())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Reports a failure when the app with the given pid/uid cannot access the requested identifier.
+ *
+ * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE
+ * permission or carrier privileges.
+ * @throws SecurityException if the caller does not meet any of the requirements for the
+ * requested identifier and is targeting Q or is targeting pre-Q
+ * and does not have the READ_PHONE_STATE permission or carrier
+ * privileges.
+ */
+ private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
+ int uid, String callingPackage, String message) {
+ if (callingPackage != null) {
+ try {
+ // if the target SDK is pre-Q then check if the calling package would have
+ // previously had access to device identifiers.
+ ApplicationInfo callingPackageInfo = context.getPackageManager().getApplicationInfo(
+ callingPackage, 0);
+ if (callingPackageInfo != null
+ && callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+ if (context.checkPermission(android.Manifest.permission.READ_PHONE_STATE, pid,
+ uid) == PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ if (SubscriptionManager.isValidSubscriptionId(subId)
+ && getCarrierPrivilegeStatus(TELEPHONY_SUPPLIER, subId, uid)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+ return false;
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // If the application info for the calling package could not be found then default
+ // to throwing the SecurityException.
+ }
+ }
+ throw new SecurityException(message + ": The user " + uid + " does not have the "
+ + "READ_PRIVILEGED_PHONE_STATE permission to access the device identifiers");
+ }
+
+ /**
* Check whether the app with the given pid/uid can read the call log.
* @return {@code true} if the specified app has the read call log permission and AppOpp granted
* to it, {@code false} otherwise.