Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc10ce1e authored by Jeff Davidson's avatar Jeff Davidson
Browse files

Allow carrier privileged apps to access Telephony/Subscription APIs.

-All public APIs in TelephonyManager which require READ_PHONE_STATE
will now also be documented to accept carrier privileged callers as
well. (One exception is the change callbacks in each, which will be
addressed in a separate CL).

-For SubscriptionManager, callers without READ_PHONE_STATE will now be
able to access the subscription list; however, the resulting list will
be filtered to only include subscriptions for which the caller has
carrier privileges.

-All @see references to hasCarrierPrivileges have been removed in
favor of an inline {@link}. The @see section is set apart from the
rest of the Javadoc and thus appears out of context of where it's
actually relevant; moreover, it is often placed in the middle of a
line which makes it invalid. Using {@link} inlines the reference where
it's actually relevant.

-@SuppressAutodoc is added to any public method which has a
@RequiresPermission declaration that isn't a sufficient description of
the allowed callers, i.e. for APIs which accept carrier-privileged
callers, or the default dialer app or other exceptional cases. This
ensures redundant (but incorrect) requires permission declarations
aren't autogenerated.

Bug: 70041899
Test: TreeHugger, unit tests in topic
Change-Id: Ia5cc145c19d99fe2b87e3425bb95281980edef6f
parent ca7e92c0
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -394,8 +394,10 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
                + " callback.asBinder=" + callback.asBinder());
        }

        // TODO(b/70041899): Find a way to make this work for carrier-privileged callers.
        if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                mContext, callingPackage, "addOnSubscriptionsChangedListener")) {
                mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
                "addOnSubscriptionsChangedListener")) {
            return;
        }

@@ -686,8 +688,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {

    private boolean canReadPhoneState(String callingPackage, String message) {
        try {
            // TODO(b/70041899): Find a way to make this work for carrier-privileged callers.
            return TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                    mContext, callingPackage, message);
                    mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message);
        } catch (SecurityException e) {
            return false;
        }
@@ -1735,8 +1738,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub {
        }

        if ((events & ENFORCE_PHONE_STATE_PERMISSION_MASK) != 0) {
            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
                    mContext, callingPackage, message)) {
            // TODO(b/70041899): Find a way to make this work for carrier-privileged callers.
            if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
                    SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage, message)) {
                return false;
            }
        }
+31 −4
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.BroadcastOptions;
@@ -42,7 +43,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.DisplayMetrics;

import com.android.internal.telephony.IOnSubscriptionsChangedListener;
@@ -59,9 +59,6 @@ import java.util.concurrent.TimeUnit;
/**
 * SubscriptionManager is the application interface to SubscriptionController
 * and provides information about the current Telephony Subscriptions.
 * <p>
 * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE unless otherwise
 * specified.
 */
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
public class SubscriptionManager {
@@ -612,6 +609,8 @@ public class SubscriptionManager {
     * @param listener an instance of {@link OnSubscriptionsChangedListener} with
     *                 onSubscriptionsChanged overridden.
     */
    // TODO(b/70041899): Find a way to extend this to carrier-privileged apps.
    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
    public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
        String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
        if (DBG) {
@@ -660,9 +659,15 @@ public class SubscriptionManager {
    /**
     * Get the active SubscriptionInfo with the input subId.
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     * or that the calling app has carrier privileges (see
     * {@link TelephonyManager#hasCarrierPrivileges}).
     *
     * @param subId The unique SubscriptionInfo key in database.
     * @return SubscriptionInfo, maybe null if its not active.
     */
    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
    public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
        if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId);
        if (!isValidSubscriptionId(subId)) {
@@ -716,9 +721,16 @@ public class SubscriptionManager {

    /**
     * Get the active SubscriptionInfo associated with the slotIndex
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     * or that the calling app has carrier privileges (see
     * {@link TelephonyManager#hasCarrierPrivileges}).
     *
     * @param slotIndex the slot which the subscription is inserted
     * @return SubscriptionInfo, maybe null if its not active
     */
    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
    public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) {
        if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex);
        if (!isValidSlotIndex(slotIndex)) {
@@ -770,6 +782,11 @@ public class SubscriptionManager {
     * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
     * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     * or that the calling app has carrier privileges (see
     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
     * to the calling app are returned.
     *
     * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
     * <ul>
     * <li>
@@ -786,6 +803,8 @@ public class SubscriptionManager {
     * </li>
     * </ul>
     */
    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
    public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
        List<SubscriptionInfo> result = null;

@@ -928,10 +947,18 @@ public class SubscriptionManager {
    }

    /**
     *
     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
     * or that the calling app has carrier privileges (see
     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include
     * only those subscriptions accessible to the caller.
     *
     * @return the current number of active subscriptions. There is no guarantee the value
     * returned by this method will be the same as the length of the list returned by
     * {@link #getActiveSubscriptionInfoList}.
     */
    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
    public int getActiveSubscriptionInfoCount() {
        int result = 0;

+197 −147

File changed.

Preview size limit exceeded, changes collapsed.

+64 −19
Original line number Diff line number Diff line
@@ -26,7 +26,8 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ITelephony;

import java.util.function.Supplier;

/** Utility class for Telephony permission enforcement. */
public final class TelephonyPermissions {
@@ -34,6 +35,9 @@ public final class TelephonyPermissions {

    private static final boolean DBG = false;

    private static final Supplier<ITelephony> TELEPHONY_SUPPLIER = () ->
            ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));

    private TelephonyPermissions() {}

    /**
@@ -41,8 +45,8 @@ public final class TelephonyPermissions {
     *
     * <p>This method behaves in one of the following ways:
     * <ul>
     *   <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the
     *       READ_PHONE_STATE runtime permission.
     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the
     *       READ_PHONE_STATE runtime permission, or carrier privileges on the given subId.
     *   <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
     *       apps which support runtime permissions, if the caller does not currently have any of
     *       these permissions.
@@ -51,10 +55,20 @@ public final class TelephonyPermissions {
     *       manually (via AppOps). In this case we can't throw as it would break app compatibility,
     *       so we return false to indicate that the calling function should return dummy data.
     * </ul>
     *
     * <p>Note: for simplicity, this method always returns false for callers using legacy
     * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged.
     * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+
     * devices.
     *
     * @param subId the subId of the relevant subscription; used to check carrier privileges. May be
     *              {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} to skip this check for cases
     *              where it isn't relevant (hidden APIs, or APIs which are otherwise okay to leave
     *              inaccesible to carrier-privileged apps).
     */
    public static boolean checkCallingOrSelfReadPhoneState(
            Context context, String callingPackage, String message) {
        return checkReadPhoneState(context, Binder.getCallingPid(), Binder.getCallingUid(),
            Context context, int subId, String callingPackage, String message) {
        return checkReadPhoneState(context, subId, Binder.getCallingPid(), Binder.getCallingUid(),
                callingPackage, message);
    }

@@ -63,8 +77,8 @@ public final class TelephonyPermissions {
     *
    * <p>This method behaves in one of the following ways:
     * <ul>
     *   <li>return true: if the caller has either the READ_PRIVILEGED_PHONE_STATE permission or the
     *       READ_PHONE_STATE runtime permission.
     *   <li>return true: if the caller has the READ_PRIVILEGED_PHONE_STATE permission, the
     *       READ_PHONE_STATE runtime permission, or carrier privileges on the given subId.
     *   <li>throw SecurityException: if the caller didn't declare any of these permissions, or, for
     *       apps which support runtime permissions, if the caller does not currently have any of
     *       these permissions.
@@ -73,9 +87,22 @@ public final class TelephonyPermissions {
     *       manually (via AppOps). In this case we can't throw as it would break app compatibility,
     *       so we return false to indicate that the calling function should return dummy data.
     * </ul>
     *
     * <p>Note: for simplicity, this method always returns false for callers using legacy
     * permissions and who have had READ_PHONE_STATE revoked, even if they are carrier-privileged.
     * Such apps should migrate to runtime permissions or stop requiring READ_PHONE_STATE on P+
     * devices.
     */
    public static boolean checkReadPhoneState(
            Context context, int pid, int uid, String callingPackage, String message) {
            Context context, int subId, int pid, int uid, String callingPackage, String message) {
        return checkReadPhoneState(
                context, TELEPHONY_SUPPLIER, subId, pid, uid, callingPackage, message);
    }

    @VisibleForTesting
    public static boolean checkReadPhoneState(
            Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
            String callingPackage, String message) {
        try {
            context.enforcePermission(
                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pid, uid, message);
@@ -83,8 +110,18 @@ public final class TelephonyPermissions {
            // SKIP checking for run-time permission since caller has PRIVILEGED permission
            return true;
        } catch (SecurityException privilegedPhoneStateException) {
            try {
                context.enforcePermission(
                        android.Manifest.permission.READ_PHONE_STATE, pid, uid, message);
            } catch (SecurityException phoneStateException) {
                // If we don't have the runtime permission, but do have carrier privileges, that
                // suffices for reading phone state.
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    enforceCarrierPrivilege(telephonySupplier, subId, uid, message);
                    return true;
                }
                throw phoneStateException;
            }
        }

        // We have READ_PHONE_STATE permission, so return true as long as the AppOps bit hasn't been
@@ -101,14 +138,16 @@ public final class TelephonyPermissions {
     * default SMS app and apps with READ_SMS or READ_PHONE_NUMBERS can also read phone numbers.
     */
    public static boolean checkCallingOrSelfReadPhoneNumber(
            Context context, String callingPackage, String message) {
            Context context, int subId, String callingPackage, String message) {
        return checkReadPhoneNumber(
                context, Binder.getCallingPid(), Binder.getCallingUid(), callingPackage, message);
                context, TELEPHONY_SUPPLIER, subId, Binder.getCallingPid(), Binder.getCallingUid(),
                callingPackage, message);
    }

    @VisibleForTesting
    public static boolean checkReadPhoneNumber(
            Context context, int pid, int uid, String callingPackage, String message) {
            Context context, Supplier<ITelephony> telephonySupplier, int subId, int pid, int uid,
            String callingPackage, String message) {
        // Default SMS app can always read it.
        AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        if (appOps.noteOp(AppOpsManager.OP_WRITE_SMS, uid, callingPackage) ==
@@ -121,7 +160,8 @@ public final class TelephonyPermissions {

        // First, check if we can read the phone state.
        try {
            return checkReadPhoneState(context, pid, uid, callingPackage, message);
            return checkReadPhoneState(
                    context, telephonySupplier, subId, pid, uid, callingPackage, message);
        } catch (SecurityException readPhoneStateSecurityException) {
        }
        // Can be read with READ_SMS too.
@@ -186,16 +226,21 @@ public final class TelephonyPermissions {
    }

    private static void enforceCarrierPrivilege(int subId, int uid, String message) {
        if (getCarrierPrivilegeStatus(subId, uid) !=
        enforceCarrierPrivilege(TELEPHONY_SUPPLIER, subId, uid, message);
    }

    private static void enforceCarrierPrivilege(
            Supplier<ITelephony> telephonySupplier, int subId, int uid, String message) {
        if (getCarrierPrivilegeStatus(telephonySupplier, subId, uid) !=
                TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
            if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
            throw new SecurityException(message);
        }
    }

    private static int getCarrierPrivilegeStatus(int subId, int uid) {
        ITelephony telephony =
                ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
    private static int getCarrierPrivilegeStatus(
            Supplier<ITelephony> telephonySupplier, int subId, int uid) {
        ITelephony telephony = telephonySupplier.get();
        try {
            if (telephony != null) {
                return telephony.getCarrierPrivilegeStatusForUid(subId, uid);