/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.metrics;

import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_IMS;
import static com.android.internal.telephony.InboundSmsHandler.SOURCE_INJECTED_FROM_UNKNOWN;
import static com.android.internal.telephony.InboundSmsHandler.SOURCE_NOT_INJECTED;
import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_SUCCESS;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_IMS;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_VOICEMAIL_INDICATION;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_WAP_PUSH;
import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_ZERO;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_GENERIC_FAILURE;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_NETWORK_ERROR;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_NONE;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_UNKNOWN;
import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SMS_TECH__SMS_TECH_UNKNOWN;

import android.annotation.Nullable;
import android.app.Activity;
import android.provider.Telephony.Sms.Intents;
import android.telephony.Annotation.NetworkType;
import android.telephony.AnomalyReporter;
import android.telephony.ServiceState;
import android.telephony.SmsManager;
import android.telephony.TelephonyManager;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.ImsSmsImplBase;
import android.telephony.ims.stub.ImsSmsImplBase.SendStatusResult;

import com.android.internal.telephony.InboundSmsHandler;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
import com.android.internal.telephony.satellite.SatelliteConstants;
import com.android.internal.telephony.satellite.SatelliteController;
import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
import com.android.telephony.Rlog;

import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;

/** Collects sms events per phone ID for the pulled atom. */
public class SmsStats {
    private static final String TAG = SmsStats.class.getSimpleName();

    /** 3GPP error for out of service: "no network service" in TS 27.005 cl 3.2.5 */
    private static final int NO_NETWORK_ERROR_3GPP = 331;

    /** 3GPP2 error for out of service: "Other radio interface problem" in N.S0005 Table 171 */
    private static final int NO_NETWORK_ERROR_3GPP2 = 66;
    public static final UUID NTN_OUTGOING_SMS_ERROR_ANOMALY_UUID = UUID.fromString(
            "bfd2a3d9-9f2c-4070-b9df-bdbb7f9c645a");

    private final Phone mPhone;

    private final PersistAtomsStorage mAtomsStorage =
            PhoneFactory.getMetricsCollector().getAtomsStorage();

    private static final Random RANDOM = new Random();


    public SmsStats(Phone phone) {
        mPhone = phone;
    }

    /** Create a new atom when multi-part incoming SMS is dropped due to missing parts. */
    public void onDroppedIncomingMultipartSms(boolean is3gpp2, int receivedCount, int totalCount,
            boolean isEmergency, int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(is3gpp2, SOURCE_NOT_INJECTED, isEmergency,
                pduLength);
        // Keep SMS tech as unknown because it's possible that it changed overtime and is not
        // necessarily the current one. Similarly mark the RAT as unknown.
        proto.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN;
        proto.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
        proto.error = INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
        proto.totalParts = totalCount;
        proto.receivedParts = receivedCount;
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an SMS for the voicemail indicator is received. */
    public void onIncomingSmsVoicemail(boolean is3gpp2,
            @InboundSmsHandler.SmsSource int smsSource, int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, false, pduLength);
        proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_VOICEMAIL_INDICATION;
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an SMS of type zero is received. */
    public void onIncomingSmsTypeZero(@InboundSmsHandler.SmsSource int smsSource, int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource, false,
                pduLength);
        proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_ZERO;
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an SMS-PP for the SIM card is received. */
    public void onIncomingSmsPP(@InboundSmsHandler.SmsSource int smsSource, boolean success,
            int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource, false,
                pduLength);
        proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
        proto.error = getIncomingSmsError(success);
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an SMS is received successfully. */
    public void onIncomingSmsSuccess(boolean is3gpp2,
            @InboundSmsHandler.SmsSource int smsSource, int messageCount,
            boolean blocked, long messageId, boolean isEmergency, int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, isEmergency, pduLength);
        proto.totalParts = messageCount;
        proto.receivedParts = messageCount;
        proto.blocked = blocked;
        proto.messageId = messageId;
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an incoming SMS has an error. */
    public void onIncomingSmsError(boolean is3gpp2,
            @InboundSmsHandler.SmsSource int smsSource, int result, boolean isEmergency,
            int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, isEmergency, pduLength);
        proto.error = getIncomingSmsError(result);
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an incoming WAP_PUSH SMS is received. */
    public void onIncomingSmsWapPush(@InboundSmsHandler.SmsSource int smsSource,
            int messageCount, int result, long messageId, boolean isEmergency, int pduLength) {
        IncomingSms proto = getIncomingDefaultProto(false, smsSource, isEmergency, pduLength);
        proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_WAP_PUSH;
        proto.totalParts = messageCount;
        proto.receivedParts = messageCount;
        proto.error = getIncomingSmsError(result);
        proto.messageId = messageId;
        mAtomsStorage.addIncomingSms(proto);
    }

    /** Create a new atom when an outgoing SMS is sent. */
    public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
            @SmsManager.Result int sendErrorCode, long messageId, boolean isFromDefaultApp,
            long intervalMillis, boolean isEmergency, boolean isMtSmsPolling, int pduLength,
            @Nullable String callingPackageName, int uid) {
        onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, sendErrorCode, NO_ERROR_CODE,
                messageId, isFromDefaultApp, intervalMillis, isEmergency, isMtSmsPolling,
                pduLength, callingPackageName, uid);
    }

    /** Create a new atom when an outgoing SMS is sent. */
    public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
            @SmsManager.Result int sendErrorCode, int networkErrorCode, long messageId,
            boolean isFromDefaultApp, long intervalMillis, boolean isEmergency,
            boolean isMtSmsPolling, int pduLength, @Nullable String callingPackageName, int uid) {
        OutgoingSms proto =
                getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp,
                        intervalMillis, isEmergency, isMtSmsPolling, pduLength, callingPackageName,
                        uid);

        // The field errorCode is used for up-to-Android-13 devices. From Android 14, sendErrorCode
        // and networkErrorCode will be used. The field errorCode will be deprecated when most
        // devices use Android 14 or higher versions.
        if (isOverIms) {
            // Populate error code and result for IMS case
            proto.errorCode = sendErrorCode;
            if (fallbackToCs) {
                proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK;
            } else if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
                proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
            } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) {
                proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
            }
        } else {
            // Populate error code and result for CS case
            if (sendErrorCode == SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY) {
                proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
            } else if (sendErrorCode != SmsManager.RESULT_ERROR_NONE) {
                proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
            }
            proto.errorCode = networkErrorCode;
            if (sendErrorCode == SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE
                    && networkErrorCode == NO_ERROR_CODE) {
                proto.errorCode = is3gpp2 ? NO_NETWORK_ERROR_3GPP2 : NO_NETWORK_ERROR_3GPP;
            }
        }

        proto.sendErrorCode = sendErrorCode;
        proto.networkErrorCode = networkErrorCode;

        mAtomsStorage.addOutgoingSms(proto);
        reportNtnOutgoingSmsAnomaly(proto);
        CarrierRoamingSatelliteSessionStats sessionStats =
                CarrierRoamingSatelliteSessionStats.getInstance(mPhone.getSubId());
        sessionStats.onOutgoingSms(mPhone.getSubId());
    }

    /** Create a new atom when user attempted to send an outgoing short code sms. */
    public void onOutgoingShortCodeSms(int category, int xmlVersion) {
        OutgoingShortCodeSms proto = new OutgoingShortCodeSms();
        proto.category = category;
        proto.xmlVersion = xmlVersion;
        proto.shortCodeSmsCount = 1;
        mAtomsStorage.addOutgoingShortCodeSms(proto);
    }

    /** Creates a proto for a normal single-part {@code IncomingSms} with default values. */
    private IncomingSms getIncomingDefaultProto(boolean is3gpp2,
            @InboundSmsHandler.SmsSource int smsSource, boolean isEmergency, int pduLength) {
        IncomingSms proto = new IncomingSms();
        proto.smsFormat = getSmsFormat(is3gpp2);
        proto.smsTech = getSmsTech(smsSource, is3gpp2);
        proto.rat = getRat(smsSource);
        proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
        proto.totalParts = 1;
        proto.receivedParts = 1;
        proto.blocked = false;
        proto.error = INCOMING_SMS__ERROR__SMS_SUCCESS;
        proto.isRoaming = getIsRoaming();
        proto.simSlotIndex = getPhoneId();
        proto.isMultiSim = SimSlotState.isMultiSim();
        proto.isEsim = SimSlotState.isEsim(getPhoneId());
        proto.carrierId = getCarrierId();
        // Message ID is initialized with random number, as it is not available for all incoming
        // SMS messages (e.g. those handled by OS or error cases).
        proto.messageId = RANDOM.nextLong();
        proto.count = 1;
        proto.isManagedProfile = mPhone.isManagedProfile();
        proto.isNtn = isInSatelliteModeForCarrierRoaming(mPhone);
        proto.isEmergency = isEmergency;
        proto.isNbIotNtn = isNbIotNtn(mPhone);
        proto.pduLength = pduLength;
        proto.plmn = getPlmn(mPhone);
        return proto;
    }

    /** Create a proto for a normal {@code OutgoingSms} with default values. */
    private OutgoingSms getOutgoingDefaultProto(boolean is3gpp2, boolean isOverIms,
            long messageId, boolean isFromDefaultApp, long intervalMillis, boolean isEmergency,
            boolean isMtSmsPolling, int pduLength, @Nullable String callingPackageName, int uid) {
        OutgoingSms proto = new OutgoingSms();
        proto.smsFormat = getSmsFormat(is3gpp2);
        proto.smsTech = getSmsTech(isOverIms, is3gpp2);
        proto.rat = getRat(isOverIms);
        proto.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
        proto.errorCode = isOverIms ? SmsManager.RESULT_ERROR_NONE : NO_ERROR_CODE;
        proto.isRoaming = getIsRoaming();
        proto.isFromDefaultApp = isFromDefaultApp;
        proto.simSlotIndex = getPhoneId();
        proto.isMultiSim = SimSlotState.isMultiSim();
        proto.isEsim = SimSlotState.isEsim(getPhoneId());
        proto.carrierId = getCarrierId();
        // If the message ID is invalid, generate a random value
        proto.messageId = messageId != 0L ? messageId : RANDOM.nextLong();
        // Setting the retry ID to zero. If needed, it will be incremented when the atom is added
        // in the persistent storage.
        proto.retryId = 0;
        proto.intervalMillis = intervalMillis;
        proto.count = 1;
        proto.isManagedProfile = mPhone.isManagedProfile();
        proto.isEmergency = isEmergency;
        proto.isNtn = isInSatelliteModeForCarrierRoaming(mPhone);
        proto.isMtSmsPolling = isMtSmsPolling;
        proto.isNbIotNtn = isNbIotNtn(mPhone);
        proto.pduLength = pduLength;
        proto.plmn = getPlmn(mPhone);
        // package name will be reported only when the sms is sent via non-terrestrial network.
        proto.callingPackageName = (isInSatelliteModeForCarrierRoaming(mPhone)
                && callingPackageName != null) ? callingPackageName : "";
        proto.appUid = uid;
        return proto;
    }

    private static int getSmsFormat(boolean is3gpp2) {
        if (is3gpp2) {
            return INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
        } else {
            return INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
        }
    }

    private int getSmsTech(@InboundSmsHandler.SmsSource int smsSource, boolean is3gpp2) {
        if (smsSource == SOURCE_INJECTED_FROM_UNKNOWN) {
            return INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN;
        }
        return getSmsTech(smsSource == SOURCE_INJECTED_FROM_IMS, is3gpp2);
    }

    private int getSmsTech(boolean isOverIms, boolean is3gpp2) {
        if (isOverIms) {
            return INCOMING_SMS__SMS_TECH__SMS_TECH_IMS;
        } else if (is3gpp2) {
            return INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
        } else {
            return INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
        }
    }

    private static int getIncomingSmsError(int result) {
        switch (result) {
            case Activity.RESULT_OK:
            case Intents.RESULT_SMS_HANDLED:
                return INCOMING_SMS__ERROR__SMS_SUCCESS;
            case Intents.RESULT_SMS_OUT_OF_MEMORY:
                return INCOMING_SMS__ERROR__SMS_ERROR_NO_MEMORY;
            case Intents.RESULT_SMS_UNSUPPORTED:
                return INCOMING_SMS__ERROR__SMS_ERROR_NOT_SUPPORTED;
            case Intents.RESULT_SMS_GENERIC_ERROR:
            default:
                return INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
        }
    }

    private static int getIncomingSmsError(boolean success) {
        if (success) {
            return INCOMING_SMS__ERROR__SMS_SUCCESS;
        } else {
            return INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
        }
    }

    private static int getOutgoingSmsError(@SendStatusResult int imsSendResult) {
        switch (imsSendResult) {
            case ImsSmsImplBase.SEND_STATUS_OK:
                return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
            case ImsSmsImplBase.SEND_STATUS_ERROR:
                return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
            case ImsSmsImplBase.SEND_STATUS_ERROR_RETRY:
                return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_RETRY;
            case ImsSmsImplBase.SEND_STATUS_ERROR_FALLBACK:
                return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR_FALLBACK;
            default:
                return OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_UNKNOWN;
        }
    }

    /**
     * Returns a hash value to identify messages that are identical for the purpose of merging them
     * together when storage is full.
     */
    static int getSmsHashCode(OutgoingSms sms) {
        return Objects.hash(sms.smsFormat, sms.smsTech, sms.rat, sms.sendResult, sms.errorCode,
                sms.isRoaming, sms.isFromDefaultApp, sms.simSlotIndex, sms.isMultiSim, sms.isEsim,
                sms.carrierId, sms.isEmergency, sms.isNtn, sms.isMtSmsPolling, sms.isNbIotNtn,
                sms.pduLength, sms.callingPackageName, sms.appUid, sms.plmn);
    }

    /**
     * Returns a hash value to identify messages that are identical for the purpose of merging them
     * together when storage is full.
     */
    static int getSmsHashCode(IncomingSms sms) {
        return Objects.hash(sms.smsFormat, sms.smsTech, sms.rat, sms.smsType,
            sms.totalParts, sms.receivedParts, sms.blocked, sms.error,
                sms.isRoaming, sms.simSlotIndex, sms.isMultiSim, sms.isEsim, sms.carrierId,
                sms.isNtn, sms.isNbIotNtn, sms.pduLength, sms.plmn);
    }

    private int getPhoneId() {
        Phone phone = mPhone;
        if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone = mPhone.getDefaultPhone();
        }
        return phone.getPhoneId();
    }

    @Nullable
    private ServiceState getServiceState() {
        Phone phone = mPhone;
        if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone = mPhone.getDefaultPhone();
        }
        ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
        return serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
    }

    private @NetworkType int getRat(@InboundSmsHandler.SmsSource int smsSource) {
        if (smsSource == SOURCE_INJECTED_FROM_UNKNOWN) {
            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
        }
        return getRat(smsSource == SOURCE_INJECTED_FROM_IMS);
    }

    private @NetworkType int getRat(boolean isOverIms) {
        if (isOverIms) {
            if (mPhone.getImsRegistrationTech()
                    == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN) {
                return TelephonyManager.NETWORK_TYPE_IWLAN;
            }
        }
        // TODO(b/168837897): Returns the RAT at the time the SMS was received..
        ServiceState serviceState = getServiceState();
        return serviceState != null
                ? serviceState.getVoiceNetworkType() : TelephonyManager.NETWORK_TYPE_UNKNOWN;
    }

    private boolean getIsRoaming() {
        ServiceState serviceState = getServiceState();
        return ServiceStateStats.isNetworkRoaming(serviceState);
    }

    private int getCarrierId() {
        Phone phone = mPhone;
        if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone = mPhone.getDefaultPhone();
        }
        return phone.getCarrierId();
    }

    private void reportNtnOutgoingSmsAnomaly(OutgoingSms proto) {
        if (!proto.isNtn && !proto.isNbIotNtn) {
            return;
        }

        if (proto.sendResult == OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS) {
            // Do not report anomaly if sendResult is success
            return;
        }

        if (proto.errorCode == OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_NONE
                && proto.smsTech != OUTGOING_SMS__SMS_TECH__SMS_TECH_UNKNOWN) {
            // Only report anomaly for errorCode==ERROR_NONE when sms_tech is unknown.
            return;
        }

        // For NTN outgoing SMS, proto.errorCode == proto.networkErrorCode
        switch (proto.errorCode) {
            case OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_NONE:
            case OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_NETWORK_ERROR:
            case OUTGOING_SMS__SEND_ERROR_CODE__SMS_SEND_ERROR_GENERIC_FAILURE:
            case SmsManager.SMS_RP_CAUSE_CONGESTION:
            case SmsManager.SMS_RP_CAUSE_PROTOCOL_ERROR:
                String message = "NTN Outgoing SMS failed";
                Rlog.d(TAG, message + " with errorCode=" + proto.errorCode
                        + " with sendResult=" + proto.sendResult + " smsTech=" + proto.smsTech);
                AnomalyReporter.reportAnomaly(generateUUID(proto.errorCode, proto.sendResult),
                        message, proto.carrierId);
        }
    }

    private UUID generateUUID(int errorCode, int sendResult) {
        long lerrorCode = errorCode;
        long lsendResult = sendResult;
        return new UUID(NTN_OUTGOING_SMS_ERROR_ANOMALY_UUID.getMostSignificantBits(),
                NTN_OUTGOING_SMS_ERROR_ANOMALY_UUID.getLeastSignificantBits()
                        + ((lsendResult << 32) + lerrorCode));
    }

    private boolean isNbIotNtn(Phone phone) {
        return SatelliteController.getInstance().isInCarrierRoamingNbIotNtn(phone);
    }

    private boolean isInSatelliteModeForCarrierRoaming(Phone phone) {
        return SatelliteController.getInstance().isInSatelliteModeForCarrierRoaming(phone);
    }

    private String getPlmn(Phone phone) {
        String plmn = Optional.ofNullable(phone)
                .map(Phone::getServiceState)
                .map(ServiceState::getOperatorNumeric)
                .orElse(SatelliteConstants.DEFAULT_PLMN);
        return plmn;
    }

    private void loge(String format, Object... args) {
        Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
    }
}
