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

Commit 97a845b6 authored by Nate Myren's avatar Nate Myren Committed by Android (Google) Code Review
Browse files

Merge "Create SmsManager APIs to assemble trusted SMS OTP UIDs" into main

parents d47841c5 69bbef11
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -3560,6 +3560,8 @@ package android.telephony {
  public final class SmsManager {
    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int checkSmsShortCodeDestination(String, String);
    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearStorageMonitorMemoryStatusOverride();
    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, "android.permission.MANAGE_COMPANION_DEVICES", android.Manifest.permission.MANAGE_ROLE_HOLDERS}) public static java.util.Set<java.lang.String> getSmsOtpTrustedPackages(@NonNull android.content.Context);
    method @RequiresPermission(allOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, "android.permission.MANAGE_COMPANION_DEVICES", android.Manifest.permission.MANAGE_ROLE_HOLDERS}) public static boolean isAppTrustedForSmsOtp(@NonNull android.content.Context, @NonNull String, int);
    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setStorageMonitorMemoryStatusOverride(boolean);
    field public static final int SMS_CATEGORY_FREE_SHORT_CODE = 1; // 0x1
    field public static final int SMS_CATEGORY_NOT_SHORT_CODE = 0; // 0x0
+1 −0
Original line number Diff line number Diff line
@@ -232,6 +232,7 @@ applications that come with the platform
        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
        <permission name="android.permission.MODIFY_PHONE_STATE"/>
        <permission name="android.permission.USE_RESERVED_DISK"/>
        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
        <!-- Permissions required for reading and logging compat changes -->
        <permission name="android.permission.LOG_COMPAT_CHANGE" />
        <permission name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+189 −0
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package android.telephony;

import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.MANAGE_ROLE_HOLDERS;
import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
import static android.Manifest.permission.RECEIVE_SENSITIVE_NOTIFICATIONS;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -26,22 +31,30 @@ import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.CursorWindow;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;

@@ -59,6 +72,8 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;

/*
@@ -289,6 +304,15 @@ public final class SmsManager {
    public static final String MMS_CONFIG_CLOSE_CONNECTION =
            CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;

    /**
     * The following roles need access to SMS messages with OTPs:
     * SMS: To handle basic SMS tasks
     * ASSISTANT: To perform actions with all SMS messages
     * DIALER: The Dialer role has SMS permissions, and is considered trusted
     */
    private static final List<String> SMS_OTP_READING_ROLES = List.of(RoleManager.ROLE_SMS,
            RoleManager.ROLE_ASSISTANT, RoleManager.ROLE_DIALER);

    /**
     * 3gpp2 SMS priority is not specified
     * @hide
@@ -436,6 +460,17 @@ public final class SmsManager {
    @SystemApi
    public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3;

    /**
     * A list of flags that should be used in the package manager to retrieve all system apps, in
     * any state
     */
    private static final int SYSTEM_APP_FLAGS = PackageManager.MATCH_SYSTEM_ONLY
            | PackageManager.MATCH_DISABLED_COMPONENTS
            | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
            | PackageManager.MATCH_KNOWN_PACKAGES
            | PackageManager.MATCH_DIRECT_BOOT_AWARE
            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;

    // result of asking the user for a subscription to perform an operation.
    private interface SubscriptionResolverResult {
        void onSuccess(int subId);
@@ -3692,4 +3727,158 @@ public final class SmsManager {
            throw new RuntimeException(ex);
        }
    }

    private static Set<String> sSystemPkgs = null;

    /**
     * Get all app IDs that are allowed to read SMS messages/receive SMS Broadcasts which contain
     * an otp code, provided said apps also have the READ_SMS/RECEIVE_SMS permissions.
     * (respectively). This method is static because SmsManager instances can be null, if the
     * phone process is unavailable.
     * @hide
     */
    @SuppressLint({"UnflaggedApi"})
    @TestApi
    @RequiresPermission(allOf = {READ_PRIVILEGED_PHONE_STATE, MANAGE_COMPANION_DEVICES,
            MANAGE_ROLE_HOLDERS})
    public static @NonNull Set<String> getSmsOtpTrustedPackages(@NonNull Context context) {
        PackageManager pm = context.getPackageManager();
        Set<String> trustedPackages = new ArraySet<>();
        final long token = Binder.clearCallingIdentity();
        try {
            Trace.beginSection("getSmsOtpTrustedPackagess");
            // All system apps have access to OTPs in SMS messages
            if (sSystemPkgs == null) {
                Trace.beginSection("getSmsOtpTrustedPackages_systemApps");
                sSystemPkgs = new ArraySet<>();
                List<PackageInfo> systemPkgs = pm.getInstalledPackages(SYSTEM_APP_FLAGS);
                for (PackageInfo pkg : systemPkgs) {
                    sSystemPkgs.add(pkg.packageName);
                }
                Trace.endSection();
            }
            trustedPackages.addAll(sSystemPkgs);

            // Certain role holders have access
            trustedPackages.addAll(getTrustedOtpSmsRolePackages(context));

            // Holders of the RECEIVE_SENSITIVE_NOTIFICATIONS permission have access
            List<PackageInfo> permissionHolders = context.getPackageManager()
                    .getPackagesHoldingPermissions(
                            new String[] {RECEIVE_SENSITIVE_NOTIFICATIONS}, 0);
            for (PackageInfo pkg : permissionHolders) {
                trustedPackages.add(pkg.packageName);
            }

            // Carrier privileged apps have access
            trustedPackages.addAll(getPackagesWithCarrierPrivileges(context));

            // Apps with a current companion device association often need to relay all sms
            // messages to the companion device
            for (AssociationInfo info : getAllCdmAssociations(context)) {
                trustedPackages.add(info.getPackageName());
            }
        } finally {
            Binder.restoreCallingIdentity(token);
            Trace.endSection();
        }
        return trustedPackages;
    }

    /**
     * Checks if a single app is allowed to to read SMS messages/receive SMS Broadcasts which
     * contain an otp code, provided said apps also have the READ_SMS/RECEIVE_SMS permissions.
     * This method is static because SmsManager instances can be null, if the phone process is
     * unavailable.
     * @hide
     */
    @SuppressLint({"UnflaggedApi"})
    @TestApi
    @RequiresPermission(allOf = {READ_PRIVILEGED_PHONE_STATE, MANAGE_COMPANION_DEVICES,
            MANAGE_ROLE_HOLDERS})
    public static boolean isAppTrustedForSmsOtp(@NonNull Context context,
            @NonNull String packageName, int uid) {
        final long token = Binder.clearCallingIdentity();
        try {
            Trace.beginSection("isAppTrustedForSmsOtp");
            // Holders of the RECEIVE_SENSITIVE_NOTIFICATIONS permission have access
            if (context.getPackageManager()
                    .checkPermission(RECEIVE_SENSITIVE_NOTIFICATIONS, packageName)
                    == PackageManager.PERMISSION_GRANTED) {
                return true;
            }

            // All system apps have access to OTPs in SMS messages
            if (isSystemApp(context, packageName)) {
                return true;
            }

            // Certain role holders have access
            if (isTrustedOtpSmsRoleHolder(context, packageName)) {
                return true;
            }

            // Carrier privileged apps have access
            if (getPackagesWithCarrierPrivileges(context).contains(packageName)) {
                return true;
            }

            // Apps with a current companion device association often need to relay all sms
            // messages to the companion device
            for (AssociationInfo info : getAllCdmAssociations(context)) {
                if (Objects.equals(info.getPackageName(), packageName)) {
                    return true;
                }
            }

        } finally {
            Trace.endSection();
            Binder.restoreCallingIdentity(token);
        }
        return false;
    }

    @SuppressLint("MissingPermission")
    private static Set<String> getTrustedOtpSmsRolePackages(Context context) {
        RoleManager rm = context.getSystemService(RoleManager.class);
        Set<String> roleHoldingPackages = new ArraySet<>();
        for (String role: SMS_OTP_READING_ROLES) {
            roleHoldingPackages.addAll(rm.getRoleHolders(role));
        }
        return roleHoldingPackages;
    }

    private static boolean isTrustedOtpSmsRoleHolder(Context context, String packageName) {
        return getTrustedOtpSmsRolePackages(context).contains(packageName);
    }

    private static boolean isSystemApp(Context context, String packageName) {
        if (sSystemPkgs != null && sSystemPkgs.contains(packageName)) {
            return true;
        } else if (sSystemPkgs == null) {
            try {
                context.getPackageManager().getPackageUid(packageName, SYSTEM_APP_FLAGS);
                // If the package isn't a system app, this method will throw an Exception
                return true;
            } catch (PackageManager.NameNotFoundException e) {
                return false;
            }
        }
        return false;
    }

    @SuppressLint("MissingPermission")
    private static Set<String> getPackagesWithCarrierPrivileges(Context context) {
        TelephonyManager tm = context.getSystemService(TelephonyManager.class);
        if (tm == null) {
            return new ArraySet<>();
        }
        return tm.getPackagesWithCarrierPrivileges();
    }

    @SuppressLint("MissingPermission")
    private static List<AssociationInfo> getAllCdmAssociations(Context context) {
        CompanionDeviceManager cdm = context.getSystemService(CompanionDeviceManager.class);
        return cdm.getAllAssociations();
    }
}