Loading proto/src/persist_atoms.proto +18 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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; } src/java/com/android/internal/telephony/InboundSmsHandler.java +73 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading @@ -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(); Loading Loading @@ -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); } Loading @@ -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); Loading @@ -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); }); } Loading Loading @@ -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++; } Loading @@ -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); Loading src/java/com/android/internal/telephony/metrics/DefaultNetworkMonitor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading src/java/com/android/internal/telephony/metrics/MetricsCollector.java +58 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 { Loading src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +92 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -232,6 +236,7 @@ public class PersistAtomsStorage { mMaxOutgoingShortCodeSms = 5; mMaxNumSatelliteStats = 5; mMaxNumDataNetworkValidation = 5; mMaxNumOtpStats = 10; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; Loading @@ -257,6 +262,7 @@ public class PersistAtomsStorage { mMaxOutgoingShortCodeSms = 10; mMaxNumSatelliteStats = 15; mMaxNumDataNetworkValidation = 15; mMaxNumOtpStats = 100; } mAtoms = loadAtomsFromFile(); Loading Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -2493,6 +2557,7 @@ public class PersistAtomsStorage { return null; } /** * Inserts a new element in a random position in an array with a maximum size. * Loading Loading @@ -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 Loading
proto/src/persist_atoms.proto +18 −0 Original line number Diff line number Diff line Loading @@ -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: Loading Loading @@ -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; }
src/java/com/android/internal/telephony/InboundSmsHandler.java +73 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"; Loading @@ -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(); Loading Loading @@ -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); } Loading @@ -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); Loading @@ -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); }); } Loading Loading @@ -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++; } Loading @@ -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); Loading
src/java/com/android/internal/telephony/metrics/DefaultNetworkMonitor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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); } Loading
src/java/com/android/internal/telephony/metrics/MetricsCollector.java +58 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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 { Loading
src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java +92 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -232,6 +236,7 @@ public class PersistAtomsStorage { mMaxOutgoingShortCodeSms = 5; mMaxNumSatelliteStats = 5; mMaxNumDataNetworkValidation = 5; mMaxNumOtpStats = 10; } else { mMaxNumVoiceCallSessions = 50; mMaxNumSms = 25; Loading @@ -257,6 +262,7 @@ public class PersistAtomsStorage { mMaxOutgoingShortCodeSms = 10; mMaxNumSatelliteStats = 15; mMaxNumDataNetworkValidation = 15; mMaxNumOtpStats = 100; } mAtoms = loadAtomsFromFile(); Loading Loading @@ -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}. Loading Loading @@ -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); Loading Loading @@ -2493,6 +2557,7 @@ public class PersistAtomsStorage { return null; } /** * Inserts a new element in a random position in an array with a maximum size. * Loading Loading @@ -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