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

Commit d3e0df84 authored by Michele Berionne's avatar Michele Berionne Committed by Chi Zhang
Browse files

Add support for new outgoing SMS metrics

Bug: 160807699
Test: manual testing
Merged-In: Ib071616de155056fdce85ac676d92ef1189c2097
Change-Id: Ic1bb8e58d90b475cc6e0acfae15c13f8bd6be230
parent f6238229
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ option java_outer_classname = "PersistAtomsProto";

// Holds atoms to store on persist storage in case of power cycle or process crash.
// NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
// Next id: 5
// Next id: 9
message PersistAtoms {
    /* Aggregated RAT usage during the call. */
    repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1;
@@ -42,6 +42,12 @@ message PersistAtoms {

    /* Timestamp of last incoming_sms pull. */
    optional int64 incoming_sms_pull_timestamp_millis = 6;

    /* Outgoing SMS statistics and information. */
    repeated OutgoingSms outgoing_sms = 7;

    /* Timestamp of last incoming_sms pull. */
    optional int64 outgoing_sms_pull_timestamp_millis = 8;
}

// The canonical versions of the following enums live in:
@@ -105,3 +111,19 @@ message IncomingSms {
    optional int32 carrier_id = 13;
    optional int64 message_id = 14;
}

message OutgoingSms {
    optional int32 sms_format = 1;
    optional int32 sms_tech = 2;
    optional int32 rat = 3;
    optional int32 send_result = 4;
    optional int32 error_code = 5;
    optional bool is_roaming = 6;
    optional bool is_from_default_app = 7;
    optional int32 sim_slot_index = 8;
    optional bool is_multi_sim = 9;
    optional bool is_esim = 10;
    optional int32 carrier_id = 11;
    optional int64 message_id = 12;
    optional int32 retry_id = 13;
}
+16 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.os.RemoteException;
import android.provider.Telephony.Sms.Intents;
import android.telephony.CarrierConfigManager;
import android.telephony.ServiceState;
import android.telephony.SmsManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.RegistrationManager;
import android.telephony.ims.aidl.IImsSmsListener;
@@ -139,7 +140,7 @@ public class ImsSmsDispatcher extends SMSDispatcher {
    private final IImsSmsListener mImsSmsListener = new IImsSmsListener.Stub() {
        @Override
        public void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
                int reason, int networkReasonCode) {
                @SmsManager.Result int reason, int networkReasonCode) {
            final long identity = Binder.clearCallingIdentity();
            try {
                logd("onSendSmsResult token=" + token + " messageRef=" + messageRef
@@ -178,6 +179,13 @@ public class ImsSmsDispatcher extends SMSDispatcher {
                        break;
                    default:
                }
                mPhone.getSmsStats().onOutgoingSms(
                        true /* isOverIms */,
                        SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                        status == ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK,
                        reason,
                        tracker.mMessageId,
                        tracker.isFromDefaultSmsApplication(mContext));
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
@@ -436,6 +444,13 @@ public class ImsSmsDispatcher extends SMSDispatcher {
            fallbackToPstn(tracker);
            mMetrics.writeImsServiceSendSms(mPhone.getPhoneId(), format,
                    ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK, tracker.mMessageId);
            mPhone.getSmsStats().onOutgoingSms(
                    true /* isOverIms */,
                    SmsConstants.FORMAT_3GPP2.equals(format),
                    true /* fallbackToCs */,
                    SmsManager.RESULT_SYSTEM_ERROR,
                    tracker.mMessageId,
                    tracker.isFromDefaultSmsApplication(mContext));
        }
    }

+67 −13
Original line number Diff line number Diff line
@@ -751,9 +751,10 @@ public abstract class SMSDispatcher extends Handler {
    protected void handleSendComplete(AsyncResult ar) {
        SmsTracker tracker = (SmsTracker) ar.userObj;
        PendingIntent sentIntent = tracker.mSentIntent;
        SmsResponse smsResponse = (SmsResponse) ar.result;

        if (ar.result != null) {
            tracker.mMessageRef = ((SmsResponse)ar.result).mMessageRef;
        if (smsResponse != null) {
            tracker.mMessageRef = smsResponse.mMessageRef;
        } else {
            Rlog.d(TAG, "SmsResponse was null");
        }
@@ -770,10 +771,17 @@ public abstract class SMSDispatcher extends Handler {
            }
            tracker.onSent(mContext);
            mPhone.notifySmsSent(tracker.mDestAddress);

            mPhone.getSmsStats().onOutgoingSms(
                    tracker.mImsRetry > 0 /* isOverIms */,
                    SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                    false /* fallbackToCs */,
                    SmsManager.RESULT_ERROR_NONE,
                    tracker.mMessageId,
                    tracker.isFromDefaultSmsApplication(mContext));
        } else {
            if (DBG) {
                Rlog.d(TAG, "SMS send failed"
                        + " id: " + tracker.mMessageId);
                Rlog.d(TAG, "SMS send failed id: " + tracker.mMessageId);
            }

            int ss = mPhone.getServiceState().getState();
@@ -796,6 +804,13 @@ public abstract class SMSDispatcher extends Handler {
            // if sms over IMS is not supported on data and voice is not available...
            if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
                tracker.onFailed(mContext, getNotInServiceError(ss), NO_ERROR_CODE);
                mPhone.getSmsStats().onOutgoingSms(
                        tracker.mImsRetry > 0 /* isOverIms */,
                        SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                        false /* fallbackToCs */,
                        getNotInServiceError(ss),
                        tracker.mMessageId,
                        tracker.isFromDefaultSmsApplication(mContext));
            } else if ((((CommandException)(ar.exception)).getCommandError()
                    == CommandException.Error.SMS_FAIL_RETRY) &&
                   tracker.mRetryCount < MAX_SEND_RETRIES) {
@@ -808,20 +823,35 @@ public abstract class SMSDispatcher extends Handler {
                //       message, depending on the failure).  Also, in some
                //       implementations this retry is handled by the baseband.
                tracker.mRetryCount++;
                int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
                mPhone.getSmsStats().onOutgoingSms(
                        tracker.mImsRetry > 0 /* isOverIms */,
                        SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                        false /* fallbackToCs */,
                        SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY,
                        errorCode,
                        tracker.mMessageId,
                        tracker.isFromDefaultSmsApplication(mContext));
            } else {
                int errorCode = NO_ERROR_CODE;
                if (ar.result != null) {
                    errorCode = ((SmsResponse)ar.result).mErrorCode;
                }
                int errorCode = (smsResponse != null) ? smsResponse.mErrorCode : NO_ERROR_CODE;
                int error = rilErrorToSmsManagerResult(((CommandException) (ar.exception))
                        .getCommandError());
                tracker.onFailed(mContext, error, errorCode);
                mPhone.getSmsStats().onOutgoingSms(
                        tracker.mImsRetry > 0 /* isOverIms */,
                        SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                        false /* fallbackToCs */,
                        error,
                        errorCode,
                        tracker.mMessageId,
                        tracker.isFromDefaultSmsApplication(mContext));
            }
        }
    }

    @SmsManager.Result
    private static int rilErrorToSmsManagerResult(CommandException.Error rilError) {
        switch (rilError) {
            case RADIO_NOT_AVAILABLE:
@@ -881,6 +911,7 @@ public abstract class SMSDispatcher extends Handler {
     * @param ss service state
     * @return The result error based on input service state for not in service error
     */
    @SmsManager.Result
    protected static int getNotInServiceError(int ss) {
        if (ss == ServiceState.STATE_POWER_OFF) {
            return RESULT_ERROR_RADIO_OFF;
@@ -1294,7 +1325,7 @@ public abstract class SMSDispatcher extends Handler {
     */
    @VisibleForTesting
    public void sendRawPdu(SmsTracker[] trackers) {
        int error = RESULT_ERROR_NONE;
        @SmsManager.Result int error = RESULT_ERROR_NONE;
        PackageInfo appInfo = null;
        if (mSmsSendDisabled) {
            Rlog.e(TAG, "Device does not support sending sms.");
@@ -1610,10 +1641,22 @@ public abstract class SMSDispatcher extends Handler {
        }
    }

    private void handleSmsTrackersFailure(SmsTracker[] trackers, int error, int errorCode) {
    private void handleSmsTrackersFailure(SmsTracker[] trackers, @SmsManager.Result int error,
            int errorCode) {
        for (SmsTracker tracker : trackers) {
            tracker.onFailed(mContext, error, errorCode);
        }
        if (trackers.length > 0) {
            // This error occurs before the SMS is sent. Make an assumption if it would have
            // been sent over IMS or not.
            mPhone.getSmsStats().onOutgoingSms(
                    isIms(),
                    SmsConstants.FORMAT_3GPP2.equals(getFormat()),
                    false /* fallbackToCs */,
                    error,
                    trackers[0].mMessageId,
                    trackers[0].isFromDefaultSmsApplication(mContext));
        }
    }

    /**
@@ -1678,6 +1721,8 @@ public abstract class SMSDispatcher extends Handler {

        public final long mMessageId;

        private Boolean mIsFromDefaultSmsApplication;

        // SMS anomaly uuid
        private final UUID mAnomalyUUID = UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886");

@@ -1725,6 +1770,16 @@ public abstract class SMSDispatcher extends Handler {
            return mAppInfo != null ? mAppInfo.packageName : null;
        }

        /** Return if the SMS was originated from the default SMS application. */
        public boolean isFromDefaultSmsApplication(Context context) {
            if (mIsFromDefaultSmsApplication == null) {
                // Perform a lazy initialization, due to the cost of the operation.
                mIsFromDefaultSmsApplication =
                        SmsApplication.isDefaultSmsApplication(context, getAppPackageName());
            }
            return mIsFromDefaultSmsApplication;
        }

        /**
         * Update the status of this message if we persisted it
         */
@@ -1775,8 +1830,7 @@ public abstract class SMSDispatcher extends Handler {
         * @return The telephony provider URI if stored
         */
        private Uri persistSentMessageIfRequired(Context context, int messageType, int errorCode) {
            if (!mIsText || !mPersistMessage ||
                    !SmsApplication.shouldWriteMessageForPackage(mAppInfo.packageName, context)) {
            if (!mIsText || !mPersistMessage || isFromDefaultSmsApplication(context)) {
                return null;
            }
            Rlog.d(TAG, "Persist SMS into "
+36 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;

import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS;
import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
@@ -35,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
import com.android.internal.util.ConcurrentUtils;
@@ -95,6 +97,7 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
            registerAtom(VOICE_CALL_RAT_USAGE, POLICY_PULL_DAILY);
            registerAtom(VOICE_CALL_SESSION, POLICY_PULL_DAILY);
            registerAtom(INCOMING_SMS, POLICY_PULL_DAILY);
            registerAtom(OUTGOING_SMS, POLICY_PULL_DAILY);
            Rlog.d(TAG, "registered");
        } else {
            Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -127,6 +130,8 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                return pullVoiceCallSessions(data);
            case INCOMING_SMS:
                return pullIncomingSms(data);
            case OUTGOING_SMS:
                return pullOutgoingSms(data);
            default:
                Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                return StatsManager.PULL_SKIP;
@@ -225,6 +230,18 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
        }
    }

    private int pullOutgoingSms(List<StatsEvent> data) {
        OutgoingSms[] smsList = mStorage.getOutgoingSms(MIN_COOLDOWN_MILLIS);
        if (smsList != null) {
            // SMS list is already shuffled when SMS were inserted
            Arrays.stream(smsList).forEach(sms -> data.add(buildStatsEvent(sms)));
            return StatsManager.PULL_SUCCESS;
        } else {
            Rlog.w(TAG, "OUTGOING_SMS pull too frequent, skipping");
            return StatsManager.PULL_SKIP;
        }
    }

    /** Registers a pulled atom ID {@code atomId} with optional {@code policy} for pulling. */
    private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy) {
        mStatsManager.setPullAtomCallback(atomId, policy, ConcurrentUtils.DIRECT_EXECUTOR, this);
@@ -293,6 +310,25 @@ public class MetricsCollector implements StatsManager.StatsPullAtomCallback {
                .build();
    }

    private static StatsEvent buildStatsEvent(OutgoingSms sms) {
        return StatsEvent.newBuilder()
                .setAtomId(OUTGOING_SMS)
                .writeInt(sms.smsFormat)
                .writeInt(sms.smsTech)
                .writeInt(sms.rat)
                .writeInt(sms.sendResult)
                .writeInt(sms.errorCode)
                .writeBoolean(sms.isRoaming)
                .writeBoolean(sms.isFromDefaultApp)
                .writeInt(sms.simSlotIndex)
                .writeBoolean(sms.isMultiSim)
                .writeBoolean(sms.isEsim)
                .writeInt(sms.carrierId)
                .writeLong(sms.messageId)
                .writeInt(sms.retryId)
                .build();
    }

    /** Returns the value rounded to the bucket. */
    private static long round(long value, long bucket) {
        return ((value + bucket / 2) / bucket) * bucket;
+70 −27
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.Context;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
import com.android.internal.telephony.nano.PersistAtomsProto.RawVoiceCallRatUsage;
import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
@@ -70,21 +71,8 @@ public class PersistAtomsStorage {

    /** Adds a call to the storage. */
    public synchronized void addVoiceCallSession(VoiceCallSession call) {
        int newLength = mAtoms.voiceCallSession.length + 1;
        if (newLength > MAX_NUM_CALL_SESSIONS) {
            // will evict one previous call randomly instead of making the array larger
            newLength = MAX_NUM_CALL_SESSIONS;
        } else {
            mAtoms.voiceCallSession = Arrays.copyOf(mAtoms.voiceCallSession, newLength);
        }
        int insertAt = 0;
        if (newLength > 1) {
            // shuffle when each call is added, or randomly replace a previous call instead if
            // MAX_NUM_CALL_SESSIONS is reached (call at the last index is evicted).
            insertAt = sRandom.nextInt(newLength);
            mAtoms.voiceCallSession[newLength - 1] = mAtoms.voiceCallSession[insertAt];
        }
        mAtoms.voiceCallSession[insertAt] = call;
        mAtoms.voiceCallSession =
                insertAtRandomPlace(mAtoms.voiceCallSession, call, MAX_NUM_CALL_SESSIONS);
        saveAtomsToFile();
    }

@@ -97,25 +85,52 @@ public class PersistAtomsStorage {

    /** Adds an incoming SMS to the storage. */
    public synchronized void addIncomingSms(IncomingSms sms) {
        int newLength = mAtoms.incomingSms.length + 1;
        if (newLength > MAX_NUM_SMS) {
        mAtoms.incomingSms = insertAtRandomPlace(mAtoms.incomingSms, sms, MAX_NUM_SMS);
        saveAtomsToFile();

        // To be removed
        Rlog.d(TAG, "Add new incoming SMS atom: " + sms.toString());
    }

    /** Adds an outgoing SMS to the storage. */
    public synchronized void addOutgoingSms(OutgoingSms sms) {
        // Update the retry id, if needed, so that it's unique and larger than all
        // previous ones. (this algorithm ignores the fact that some SMS atoms might
        // be dropped due to limit in size of the array).
        for (OutgoingSms storedSms : mAtoms.outgoingSms) {
            if (storedSms.messageId == sms.messageId
                    && storedSms.retryId >= sms.retryId) {
                sms.retryId = storedSms.retryId + 1;
            }
        }

        mAtoms.outgoingSms = insertAtRandomPlace(mAtoms.outgoingSms, sms, MAX_NUM_SMS);
        saveAtomsToFile();

        // To be removed
        Rlog.d(TAG, "Add new outgoing SMS atom: " + sms.toString());
    }

    /** Inserts a new element in a random position in an array with a maximum size. */
    private static <T> T[] insertAtRandomPlace(T[] storage, T instance, int maxLength) {
        T[] result;
        int newLength = storage.length + 1;
        if (newLength > maxLength) {
            // will evict one previous call randomly instead of making the array larger
            newLength = MAX_NUM_SMS;
            newLength = maxLength;
            result = storage;
        } else {
            mAtoms.incomingSms = Arrays.copyOf(mAtoms.incomingSms, newLength);
            result = Arrays.copyOf(storage, newLength);
        }
        int insertAt = 0;
        if (newLength > 1) {
            // shuffle when each call is added, or randomly replace a previous call instead if
            // MAX_NUM_SMS is reached (call at the last index is evicted).
            // shuffle when each element is added, or randomly replace a previous element instead
            // if maxLength is reached (entry at the last index is evicted).
            insertAt = sRandom.nextInt(newLength);
            mAtoms.incomingSms[newLength - 1] = mAtoms.incomingSms[insertAt];
            result[newLength - 1] = result[insertAt];
        }
        mAtoms.incomingSms[insertAt] = sms;
        saveAtomsToFile();

        // To be removed
        Rlog.d(TAG, "Add new SMS atom: " + sms.toString());
        result[insertAt] = instance;
        return result;
    }

    /**
@@ -171,6 +186,23 @@ public class PersistAtomsStorage {
        }
    }

    /**
     * Returns and clears the outgoing SMS if last pulled longer than {@code
     * minIntervalMillis} ago, otherwise returns {@code null}.
     */
    @Nullable
    public synchronized OutgoingSms[] getOutgoingSms(long minIntervalMillis) {
        if (getWallTimeMillis() - mAtoms.outgoingSmsPullTimestampMillis > minIntervalMillis) {
            mAtoms.outgoingSmsPullTimestampMillis = getWallTimeMillis();
            OutgoingSms[] previousOutgoingSms = mAtoms.outgoingSms;
            mAtoms.outgoingSms = new OutgoingSms[0];
            saveAtomsToFile();
            return previousOutgoingSms;
        } else {
            return null;
        }
    }

    /** Loads {@link PersistAtoms} from a file in private storage. */
    private PersistAtoms loadAtomsFromFile() {
        try {
@@ -195,6 +227,13 @@ public class PersistAtomsStorage {
                atomsFromFile.incomingSms =
                        Arrays.copyOf(atomsFromFile.incomingSms, MAX_NUM_SMS);
            }
            if (atomsFromFile.outgoingSms == null) {
                atomsFromFile.outgoingSms = new OutgoingSms[0];
            }
            if (atomsFromFile.outgoingSms.length > MAX_NUM_SMS) {
                atomsFromFile.outgoingSms =
                        Arrays.copyOf(atomsFromFile.outgoingSms, MAX_NUM_SMS);
            }
            // out of caution, set timestamps to now if they are missing
            if (atomsFromFile.rawVoiceCallRatUsagePullTimestampMillis == 0L) {
                atomsFromFile.rawVoiceCallRatUsagePullTimestampMillis = getWallTimeMillis();
@@ -205,6 +244,9 @@ public class PersistAtomsStorage {
            if (atomsFromFile.incomingSmsPullTimestampMillis == 0L) {
                atomsFromFile.incomingSmsPullTimestampMillis = getWallTimeMillis();
            }
            if (atomsFromFile.outgoingSmsPullTimestampMillis == 0L) {
                atomsFromFile.outgoingSmsPullTimestampMillis = getWallTimeMillis();
            }
            return atomsFromFile;
        } catch (IOException | NullPointerException e) {
            Rlog.e(TAG, "cannot load/parse PersistAtoms", e);
@@ -229,6 +271,7 @@ public class PersistAtomsStorage {
        atoms.rawVoiceCallRatUsagePullTimestampMillis = currentTime;
        atoms.voiceCallSessionPullTimestampMillis = currentTime;
        atoms.incomingSmsPullTimestampMillis = currentTime;
        atoms.outgoingSmsPullTimestampMillis = currentTime;
        Rlog.d(TAG, "created new PersistAtoms");
        return atoms;
    }
Loading