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

Commit c871056b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Log OTP evaluation and redaction events upon getting SMS messages" into main

parents cf00c3c9 a99d008c
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -267,6 +267,14 @@ message PersistAtoms {

    /* Timestamp of last satellite access controller pull. */
    optional int64 satellite_access_controller_pull_timestamp_millis = 81;

    repeated OtpEvaluationEvent otp_evaluation_event = 82;

    optional int64 otp_evaluation_event_pull_timestamp_millis = 83;

    repeated OtpRedactionEvent otp_redaction_event = 84;

    optional int64 otp_redaction_event_pull_timestamp_millis = 85;
}

// The canonical versions of the following enums live in:
@@ -907,3 +915,13 @@ message SatelliteAccessController {
    optional int32 triggering_event = 11;
    optional bool is_ntn_only_carrier = 12;
}

message OtpEvaluationEvent {
    optional int32 redaction_time_ms = 1;
    optional int32 result = 2;
}

message OtpRedactionEvent {
    optional int32 uid = 1;
    optional int32 count = 4;
}
+73 −6
Original line number Diff line number Diff line
@@ -28,6 +28,10 @@ import static android.provider.Telephony.Sms.Intents.SMS_RECEIVED_ACTION;
import static android.service.carrier.CarrierMessagingService.RECEIVE_OPTIONS_SKIP_NOTIFY_WHEN_CREDENTIAL_PROTECTED_STORAGE_UNAVAILABLE;
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;

import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_HAS_OTP;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_NOT_CHECKED;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_NO_OTP;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -49,6 +53,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
@@ -60,6 +65,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerWhitelistManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
@@ -83,7 +89,9 @@ import com.android.internal.telephony.SmsConstants.MessageClass;
import com.android.internal.telephony.analytics.TelephonyAnalytics;
import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
import com.android.internal.telephony.flags.FeatureFlags;
import com.android.internal.telephony.metrics.PersistAtomsStorage;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.PersistAtomsProto;
import com.android.internal.telephony.satellite.SatelliteController;
import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
import com.android.internal.telephony.util.NotificationChannelController;
@@ -238,6 +246,8 @@ public abstract class InboundSmsHandler extends StateMachine {

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected final Context mContext;

    protected final PackageManager mPackageManager;
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private final ContentResolver mResolver;

@@ -306,8 +316,11 @@ public abstract class InboundSmsHandler extends StateMachine {

    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());

    private final PersistAtomsStorage mAtomsStorage;

    private TextClassifier mTextClassifier;


    private static String ACTION_OPEN_SMS_APP =
        "com.android.internal.telephony.OPEN_DEFAULT_SMS_APP";

@@ -330,10 +343,12 @@ public abstract class InboundSmsHandler extends StateMachine {

        mFeatureFlags = featureFlags;
        mContext = context;
        mPackageManager = context.getPackageManager();
        mStorageMonitor = storageMonitor;
        mPhone = phone;
        mResolver = context.getContentResolver();
        mWapPush = new WapPushOverSms(context, mFeatureFlags);
        mAtomsStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();

        TelephonyManager telephonyManager = TelephonyManager.from(mContext);
        boolean smsCapable = telephonyManager.isDeviceSmsCapable();
@@ -1526,6 +1541,11 @@ public abstract class InboundSmsHandler extends StateMachine {
                                null /* initialExtras */, opts);
            } catch (PackageManager.NameNotFoundException ignored) {
            }
            PersistAtomsProto.OtpEvaluationEvent evaluationEvent =
                    new PersistAtomsProto.OtpEvaluationEvent();
            evaluationEvent.result = SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_NOT_CHECKED;
            evaluationEvent.redactionTimeMs = -1;
            mAtomsStorage.addOtpEvaluationEvent(evaluationEvent);
        } else {
            checkOtpAndSendBroadcast(intent, permission, appOp, opts, resultReceiver, user);
        }
@@ -1533,6 +1553,7 @@ public abstract class InboundSmsHandler extends StateMachine {

    private void checkOtpAndSendBroadcast(Intent intent, String permission, String appOp,
            Bundle opts, SmsBroadcastReceiver resultReceiver, UserHandle user) {
        final long start = SystemClock.elapsedRealtime();
        mBackgroundExecutor.execute(() -> {
            AtomicBoolean containsOtpCallPending = new AtomicBoolean(true);
            AtomicBoolean sentBroadcastAfterWaitingMaxTime = new AtomicBoolean(false);
@@ -1547,18 +1568,27 @@ public abstract class InboundSmsHandler extends StateMachine {
            }, MAXIMUM_BROADCAST_DELAY_TIME_MS);
            boolean containsOtp = containsOtp(intent);
            containsOtpCallPending.set(false);
            int classificationTime = (int)
                    (Math.min(SystemClock.elapsedRealtime() - start, Integer.MAX_VALUE));
            if (sentBroadcastAfterWaitingMaxTime.get()) {
                // Broadcast was already sent, don't re-send
                return;
            }
            int result;
            if (containsOtp) {
                sendBroadcastWithRedactedPermissionChecks(intent, permission, appOp, opts,
                sendBroadcastToTrustedPackages(intent, permission, appOp, opts,
                        resultReceiver, user);
                result = SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_HAS_OTP;
            } else {
                sendBroadcastWithStandardPermissions(intent, permission, appOp, opts,
                        resultReceiver, user);
                result = SMS_OTP_EVALUATION__RESULT__EVALUATION_RESULT_NO_OTP;
            }

            PersistAtomsProto.OtpEvaluationEvent evaluationEvent =
                    new PersistAtomsProto.OtpEvaluationEvent();
            evaluationEvent.result = result;
            evaluationEvent.redactionTimeMs = classificationTime;
            mAtomsStorage.addOtpEvaluationEvent(evaluationEvent);
        });
    }

@@ -1607,12 +1637,12 @@ public abstract class InboundSmsHandler extends StateMachine {
    }

    @SuppressLint("MissingPermission")
    private void sendBroadcastWithRedactedPermissionChecks(Intent intent, String permission,
    private void sendBroadcastToTrustedPackages(Intent intent, String permission,
            String appOp, Bundle opts, SmsBroadcastReceiver resultReceiver, UserHandle user) {
        Set<String> trustedPackagess = SmsManager.getSmsOtpTrustedPackages(mContext);
        String[] trustedPackagesArray = new String[trustedPackagess.size()];
        Set<String> trustedPackages = SmsManager.getSmsOtpTrustedPackages(mContext);
        final String[] trustedPackagesArray = new String[trustedPackages.size()];
        int i = 0;
        for (String trusted: trustedPackagess) {
        for (String trusted: trustedPackages) {
            trustedPackagesArray[i] = trusted;
            i++;
        }
@@ -1624,10 +1654,47 @@ public abstract class InboundSmsHandler extends StateMachine {
                    .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
                            resultReceiver, getHandler(), null /* initialData */,
                            null /* initialExtras */, options.toBundle());
            logRedactedPackages(intent, permission, user, trustedPackages);
        } catch (PackageManager.NameNotFoundException ignored) {
        }
    }

    private void logRedactedPackages(Intent intent, String permission, UserHandle user,
            Set<String> trustedPackages) {
        mBackgroundExecutor.execute(() -> {
            // Aggregate all apps listening for the SMS_RECEIVED broadcast, with the RECEIVE_SMS
            // permission, filter out trusted ones, and log metrics for the remaining
            PackageManager userPm = mContext.createContextAsUser(user, 0).getPackageManager();
            List<ResolveInfo> receivers = userPm.queryBroadcastReceivers(intent,
                    PackageManager.MATCH_ALL);
            List<String> redactedPackages = new ArrayList<>();
            for (ResolveInfo info: receivers) {
                if (mPackageManager.checkPermission(permission, info.resolvePackageName)
                        != PackageManager.PERMISSION_GRANTED) {
                    continue;
                }
                if (trustedPackages.contains(info.resolvePackageName)) {
                    continue;
                }
                redactedPackages.add(info.resolvePackageName);
            }
            for (String redactedPackage: redactedPackages) {
                int uid;
                try {
                    uid = mContext.getPackageManager().getPackageUid(redactedPackage,
                            PackageManager.MATCH_ALL);
                } catch (PackageManager.NameNotFoundException e) {
                    continue;
                }
                PersistAtomsProto.OtpRedactionEvent event =
                        new PersistAtomsProto.OtpRedactionEvent();
                event.uid = uid;
                event.count = 1;
                mAtomsStorage.addOtpRedactionEvent(event);
            }
        });
    }

    private boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
        final List<UserManager.EnforcingUser> sources = mUserManager
                .getUserRestrictionSources(restrictionKey, userHandle);
+2 −1
Original line number Diff line number Diff line
@@ -63,7 +63,8 @@ public class DefaultNetworkMonitor extends Handler {
    }

    DefaultNetworkMonitor(@NonNull Context context, @NonNull FeatureFlags featureFlags) {
        super(Looper.myLooper());
        // Looper.myLooper is only null during mock tests
        super(Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper());
        registerSystemDefaultNetworkCallback(context);
    }

+58 −0
Original line number Diff line number Diff line
@@ -53,6 +53,10 @@ import static com.android.internal.telephony.TelephonyStatsLog.SIP_DELEGATE_STAT
import static com.android.internal.telephony.TelephonyStatsLog.SIP_MESSAGE_RESPONSE;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_FEATURE_TAG_STATS;
import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_EVALUATION;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_REDACTED;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_REDACTED__REDACTION_ALGORITHM__REDACTION_ALGORITHM_SMS_RETRIEVER;
import static com.android.internal.telephony.TelephonyStatsLog.SMS_OTP_REDACTED__REDACTION_LOCATION__REDACTION_LOCATION_SMS_BROADCAST;
import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
import static com.android.internal.telephony.TelephonyStatsLog.TELEPHONY_NETWORK_REQUESTS_V2;
import static com.android.internal.telephony.TelephonyStatsLog.UCE_EVENT_STATS;
@@ -91,6 +95,8 @@ import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStat
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
import com.android.internal.telephony.nano.PersistAtomsProto.OtpEvaluationEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.OtpRedactionEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
@@ -245,6 +251,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
            registerAtom(SATELLITE_ENTITLEMENT);
            registerAtom(SATELLITE_CONFIG_UPDATER);
            registerAtom(SATELLITE_ACCESS_CONTROLLER);
            registerAtom(SMS_OTP_EVALUATION);
            registerAtom(SMS_OTP_REDACTED);
            Rlog.d(TAG, "registered");
        } else {
            Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -351,6 +359,10 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                return pullSatelliteConfigUpdater(data);
            case SATELLITE_ACCESS_CONTROLLER:
                return pullSatelliteAccessController(data);
            case SMS_OTP_EVALUATION:
                return pullOtpEvaluationEvent(data);
            case SMS_OTP_REDACTED:
                return pullOtpRedactionEvent(data);
            default:
                Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                return StatsManager.PULL_SKIP;
@@ -1069,6 +1081,32 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
        }
    }

    private int pullOtpRedactionEvent(List<StatsEvent> data) {
        OtpRedactionEvent[] otpRedactionEvents =
                mStorage.getOtpRedactionEventStats(MIN_COOLDOWN_MILLIS);
        if (otpRedactionEvents != null) {
            Arrays.stream(otpRedactionEvents)
                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
        } else {
            Rlog.w(TAG, "OTP_REDACTION_EVENT pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
        return StatsManager.PULL_SUCCESS;
    }

    private int pullOtpEvaluationEvent(List<StatsEvent> data) {
        OtpEvaluationEvent[] otpEvaluationEvents =
                mStorage.getOtpEvaluationEventStats(MIN_COOLDOWN_MILLIS);
        if (otpEvaluationEvents != null) {
            Arrays.stream(otpEvaluationEvents)
                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
        } else {
            Rlog.w(TAG, "OTP_EVALUATION_EVENT pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
        return StatsManager.PULL_SUCCESS;
    }

    /** Registers a pulled atom ID {@code atomId}. */
    private void registerAtom(int atomId) {
        mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -1706,6 +1744,26 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                stats.isNtnOnlyCarrier);
    }

    private static StatsEvent buildStatsEvent(OtpRedactionEvent event) {
        return TelephonyStatsLog.buildStatsEvent(
                SMS_OTP_REDACTED,
                event.uid,
                SMS_OTP_REDACTED__REDACTION_ALGORITHM__REDACTION_ALGORITHM_SMS_RETRIEVER,
                SMS_OTP_REDACTED__REDACTION_LOCATION__REDACTION_LOCATION_SMS_BROADCAST,
                event.count
        );
    }

    private static StatsEvent buildStatsEvent(OtpEvaluationEvent event) {
        return TelephonyStatsLog.buildStatsEvent(
                SMS_OTP_REDACTED,
                event.result,
                event.redactionTimeMs,
                SMS_OTP_REDACTED__REDACTION_ALGORITHM__REDACTION_ALGORITHM_SMS_RETRIEVER,
                SMS_OTP_REDACTED__REDACTION_LOCATION__REDACTION_LOCATION_SMS_BROADCAST
        );
    }

    /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
    static Phone[] getPhonesIfAny() {
        try {
+92 −0
Original line number Diff line number Diff line
@@ -45,6 +45,8 @@ import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStat
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.NetworkRequestsV2;
import com.android.internal.telephony.nano.PersistAtomsProto.OtpEvaluationEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.OtpRedactionEvent;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
@@ -182,6 +184,8 @@ public class PersistAtomsStorage {
    /** Maximum number of data network validation to store during pulls. */
    private final int mMaxNumDataNetworkValidation;

    private final int mMaxNumOtpStats;

    /** Stores persist atoms and persist states of the puller. */
    @VisibleForTesting protected PersistAtoms mAtoms;

@@ -232,6 +236,7 @@ public class PersistAtomsStorage {
            mMaxOutgoingShortCodeSms = 5;
            mMaxNumSatelliteStats = 5;
            mMaxNumDataNetworkValidation = 5;
            mMaxNumOtpStats = 10;
        } else {
            mMaxNumVoiceCallSessions = 50;
            mMaxNumSms = 25;
@@ -257,6 +262,7 @@ public class PersistAtomsStorage {
            mMaxOutgoingShortCodeSms = 10;
            mMaxNumSatelliteStats = 15;
            mMaxNumDataNetworkValidation = 15;
            mMaxNumOtpStats = 100;
        }

        mAtoms = loadAtomsFromFile();
@@ -933,6 +939,27 @@ public class PersistAtomsStorage {
        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
    }

    /** Adds a new {@link OtpEvaluationEvent} to the storage. */
    public synchronized void addOtpEvaluationEvent(OtpEvaluationEvent stats) {
        mAtoms.otpEvaluationEvent = insertAtRandomPlace(mAtoms.otpEvaluationEvent, stats,
                mMaxNumOtpStats);
        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
    }

    /** Adds a new {@link OtpRedactionEvent} to the storage. */
    public synchronized void addOtpRedactionEvent(OtpRedactionEvent stats) {
        for (OtpRedactionEvent existingStats : mAtoms.otpRedactionEvent) {
            if (stats.uid == existingStats.uid) {
                existingStats.count += 1;
                saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
                return;
            }
        }
        mAtoms.otpRedactionEvent = insertAtRandomPlace(mAtoms.otpRedactionEvent, stats,
                mMaxNumOtpStats);
        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
    }

    /**
     * Returns and clears the voice call sessions if last pulled longer than {@code
     * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1707,6 +1734,43 @@ public class PersistAtomsStorage {
        }
    }

    /**
     * Returns and clears the {@link OtpEvaluationEvent} stats if last pulled longer
     * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
     */
    @Nullable
    public synchronized OtpEvaluationEvent[] getOtpEvaluationEventStats(
            long minIntervalMillis) {
        if ((getWallTimeMillis() - mAtoms.otpEvaluationEventPullTimestampMillis)
                > minIntervalMillis) {
            mAtoms.otpEvaluationEventPullTimestampMillis = getWallTimeMillis();
            OtpEvaluationEvent[] statsArray = mAtoms.otpEvaluationEvent;
            mAtoms.otpEvaluationEvent = new OtpEvaluationEvent[0];
            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
            return statsArray;
        } else {
            return null;
        }
    }

    /**
     * Returns and clears the {@link OtpRedactionEvent} stats if last pulled longer
     * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
     */
    @Nullable
    public synchronized OtpRedactionEvent[] getOtpRedactionEventStats(
            long minIntervalMillis) {
        if (getWallTimeMillis() - mAtoms.otpRedactionEventPullTimestampMillis > minIntervalMillis) {
            mAtoms.otpRedactionEventPullTimestampMillis = getWallTimeMillis();
            OtpRedactionEvent[] statsArray = mAtoms.otpRedactionEvent;
            mAtoms.otpRedactionEvent = new OtpRedactionEvent[0];
            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
            return statsArray;
        } else {
            return null;
        }
    }

    /** Saves {@link PersistAtoms} to a file in private storage immediately. */
    public synchronized void flushAtoms() {
        saveAtomsToFile(0);
@@ -2493,6 +2557,7 @@ public class PersistAtomsStorage {
        return null;
    }


    /**
     * Inserts a new element in a random position in an array with a maximum size.
     *
@@ -2650,6 +2715,33 @@ public class PersistAtomsStorage {
            // even if the new call is not an emergency call.
        }

        if (array instanceof OtpEvaluationEvent[]) {
            // for otp evaluation events, don't evict the maximum redaction time for each result
            OtpEvaluationEvent[] arr = (OtpEvaluationEvent[]) array;
            SparseIntArray idxOfMaxValueForResult = new SparseIntArray();
            for (int i = 0; i < arr.length; i++) {
                OtpEvaluationEvent storedEvent = arr[i];
                int maxIdx = idxOfMaxValueForResult.get(storedEvent.result, -1);
                if (maxIdx < 0 || storedEvent.redactionTimeMs > arr[maxIdx].redactionTimeMs) {
                    idxOfMaxValueForResult.put(storedEvent.result, i);
                }
            }

            int rand = sRandom.nextInt(array.length);
            // If we have as many result types as there are spots in the array, then they are all
            // maximums. Evict at random.
            if (array.length == idxOfMaxValueForResult.size()) {
                return rand;
            }

            // Else, If we randomly picked an index of one of our maximum values, then cycle through
            // the array until we find an index that isn't a maximum.
            while (idxOfMaxValueForResult.indexOfValue(rand) != -1) {
                rand = (rand + 1) % array.length;
            }
            return rand;
        }

        return sRandom.nextInt(array.length);
    }

Loading