/*
 * 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 android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERING;
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS;
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static android.util.Patterns.EMAIL_ADDRESS;

import android.annotation.Nullable;
import android.os.SystemClock;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.TransportType;
import android.telephony.Annotation.NetworkType;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ImsRegistrationAttributes;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.RegistrationManager.ImsRegistrationState;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability;
import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
import com.android.telephony.Rlog;

import java.util.regex.Pattern;

/** Tracks IMS registration metrics for each phone. */
public class ImsStats {
    private static final String TAG = ImsStats.class.getSimpleName();

    /**
     * Minimal duration of the registration state.
     *
     * <p>Registration state (including changes in capable/available features) with duration shorter
     * than this will be ignored as they are considered transient states.
     */
    private static final long MIN_REGISTRATION_DURATION_MILLIS = 1L * SECOND_IN_MILLIS;

    /**
     * Maximum length of the extra message in the termination reason.
     *
     * <p>If the extra message is longer than this length, it will be truncated.
     */
    private static final int MAX_EXTRA_MESSAGE_LENGTH = 128;

    /** Pattern used to match UUIDs in IMS extra messages for filtering. */
    private static final Pattern PATTERN_UUID =
            Pattern.compile(
                    "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");

    /** Replacement for UUIDs. */
    private static final String REPLACEMENT_UUID = "<UUID_REDACTED>";

    /**
     * Pattern used to match URI (e.g. sip, tel) in IMS extra messages for filtering.
     *
     * <p>NOTE: this simple pattern aims to catch the most common URI schemes. It is not meant to be
     * RFC-complaint.
     */
    private static final Pattern PATTERN_URI =
            Pattern.compile("([a-zA-Z]{2,}:)" + EMAIL_ADDRESS.pattern());

    /** Replacement for URI. */
    private static final String REPLACEMENT_URI = "$1<REDACTED>";

    /**
     * Pattern used to match IPv4 addresses in IMS extra messages for filtering.
     *
     * <p>This is a copy of {@code android.util.Patterns.IP_ADDRESS}, which is deprecated and might
     * be removed in the future.
     */
    private static final Pattern PATTERN_IPV4 =
            Pattern.compile(
                    "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]"
                            + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]"
                            + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
                            + "|[1-9][0-9]|[0-9]))");

    /** Replacement for IPv4 addresses. */
    private static final String REPLACEMENT_IPV4 = "<IPV4_REDACTED>";

    /**
     * Pattern used to match IPv6 addresses in IMS extra messages for filtering.
     *
     * <p>NOTE: this pattern aims to catch the most common IPv6 addresses. It is not meant to be
     * RFC-complaint or free of false positives.
     */
    private static final Pattern PATTERN_IPV6 =
            Pattern.compile(
                    // Full address
                    "([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}"
                            // Abbreviated address, e.g. 2001:4860:4860::8888
                            + "|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1,6}"
                            // Abbreviated network address, e.g. 2607:F8B0::
                            + "|([0-9a-fA-F]{1,4}:){1,7}:"
                            // Abbreviated address, e.g. ::1
                            + "|:(:[0-9a-fA-F]{1,4}){1,7}");

    /** Replacement for IPv6 addresses. */
    private static final String REPLACEMENT_IPV6 = "<IPV6_REDACTED>";

    /**
     * Pattern used to match potential IMEI values in IMS extra messages for filtering.
     *
     * <p>This includes segmented IMEI or IMEI/SV, as well as unsegmented IMEI/SV.
     */
    private static final Pattern PATTERN_IMEI =
            Pattern.compile(
                    "(^|[^0-9])(?:"
                            // IMEI, AABBBBBB-CCCCCC-D format; IMEI/SV, AABBBBBB-CCCCCC-EE format
                            + "[0-9]{8}-[0-9]{6}-[0-9][0-9]?"
                            // IMEI, AA-BBBBBB-CCCCCC-D format; IMEI/SV, AA-BBBBBB-CCCCCC-EE format
                            + "|[0-9]{2}-[0-9]{6}-[0-9]{6}-[0-9][0-9]?"
                            // IMEI/SV, unsegmented
                            + "|[0-9]{16}"
                            + ")($|[^0-9])");

    /** Replacement for IMEI. */
    private static final String REPLACEMENT_IMEI = "$1<IMEI_REDACTED>$2";

    /**
     * Pattern used to match potential unsegmented IMEI/IMSI values in IMS extra messages for
     * filtering.
     */
    private static final Pattern PATTERN_UNSEGMENTED_IMEI_IMSI =
            Pattern.compile("(^|[^0-9])[0-9]{15}($|[^0-9])");

    /** Replacement for unsegmented IMEI/IMSI. */
    private static final String REPLACEMENT_UNSEGMENTED_IMEI_IMSI = "$1<IMEI_IMSI_REDACTED>$2";

    /**
     * Pattern used to match hostnames in IMS extra messages for filtering.
     *
     * <p>This pattern differs from {@link android.util.Patterns.DOMAIN_NAME} in a few ways: it
     * requires the name to have at least 3 segments (shorter names are nearly always public or
     * typos, i.e. missing space after period), does not check the validity of TLDs, and does not
     * support punycodes in TLDs.
     */
    private static final Pattern PATTERN_HOSTNAME =
            Pattern.compile("([0-9a-zA-Z][0-9a-zA-Z_\\-]{0,61}[0-9a-zA-Z]\\.){2,}[a-zA-Z]{2,}");

    /** Replacement for hostnames. */
    private static final String REPLACEMENT_HOSTNAME = "<HOSTNAME_REDACTED>";

    /**
     * Pattern used to match potential IDs in IMS extra messages for filtering.
     *
     * <p>This pattern target numbers that are potential IDs in unknown formats. It should be
     * replaced after all other replacements are done to ensure complete and correct filtering.
     *
     * <p>Specifically, this pattern looks for any number (including hex) that is separated by dots
     * or dashes has at least 6 significant digits, and any unsegmented numbers that has at least 5
     * significant digits.
     */
    private static final Pattern PATTERN_UNKNOWN_ID =
            Pattern.compile(
                    "(^|[^0-9a-fA-F])(([-\\.]?0)*[1-9a-fA-F]([-\\.]?[0-9a-fA-F]){5,}"
                            + "|0*[1-9a-fA-F]([0-9a-fA-F]){4,})");

    /** Replacement for potential IDs. */
    private static final String REPLACEMENT_UNKNOWN_ID = "$1<ID_REDACTED>";

    private final ImsPhone mPhone;
    private final PersistAtomsStorage mStorage;

    @ImsRegistrationState private int mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;

    private long mLastTimestamp;
    private ImsRegistrationStats mLastRegistrationStats;
    @TransportType int mLastTransportType = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
    // Available features are those reported by ImsService to be available for use.
    private MmTelCapabilities mLastAvailableFeatures = new MmTelCapabilities();

    // Capable features (enabled by device/carrier). Theses are available before IMS is registered
    // and not necessarily updated when RAT changes.
    private final MmTelCapabilities mLastWwanCapableFeatures = new MmTelCapabilities();
    private final MmTelCapabilities mLastWlanCapableFeatures = new MmTelCapabilities();

    public ImsStats(ImsPhone phone) {
        mPhone = phone;
        mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage();

        mLastRegistrationStats = getDefaultImsRegistrationStats();
        updateImsRegistrationStats();
        mLastTimestamp = getTimeMillis();
    }

    /**
     * Finalizes the durations of the current IMS registration stats segment.
     *
     * <p>This method is also invoked whenever the registration state, feature capability, or
     * feature availability changes.
     */
    public synchronized void conclude() {
        long now = getTimeMillis();

        long duration = now - mLastTimestamp;
        if (duration < MIN_REGISTRATION_DURATION_MILLIS) {
            logw("conclude: discarding transient stats, duration=%d", duration);
        } else {
            ImsRegistrationStats stats = copyOfDimensionsOnly(mLastRegistrationStats);

            if (stats.rat == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
                logw("conclude: discarding UNKNOWN RAT, duration=%d", duration);
                mLastTimestamp = now;
                return;
            }

            stats.registeredTimes = mLastRegistrationStats.registeredTimes;
            // initialize registeredTimes after copying mLastRegistrationStats to be updated
            mLastRegistrationStats.registeredTimes = 0;

            switch (mLastRegistrationState) {
                case REGISTRATION_STATE_REGISTERED:
                    stats.registeredMillis = duration;

                    stats.voiceAvailableMillis =
                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
                    stats.videoAvailableMillis =
                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
                    stats.utAvailableMillis =
                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
                    stats.smsAvailableMillis =
                            mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;

                    MmTelCapabilities lastCapableFeatures =
                            stats.rat == TelephonyManager.NETWORK_TYPE_IWLAN
                                    ? mLastWlanCapableFeatures
                                    : mLastWwanCapableFeatures;
                    stats.voiceCapableMillis =
                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0;
                    stats.videoCapableMillis =
                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0;
                    stats.utCapableMillis =
                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0;
                    stats.smsCapableMillis =
                            lastCapableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0;
                    break;
                case REGISTRATION_STATE_REGISTERING:
                    stats.registeringMillis = duration;
                    break;
                case REGISTRATION_STATE_NOT_REGISTERED:
                    stats.unregisteredMillis = duration;
                    break;
            }
            mStorage.addImsRegistrationStats(stats);
        }

        mLastTimestamp = now;
    }

    /** Updates the stats when registered features changed. */
    public synchronized void onImsCapabilitiesChanged(
            @ImsRegistrationTech int radioTech, MmTelCapabilities capabilities) {
        conclude();

        boolean ratChanged = false;
        @NetworkType int newRat = convertRegistrationTechToNetworkType(radioTech);
        mLastTransportType =
                (newRat == TelephonyManager.NETWORK_TYPE_IWLAN)
                        ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
                        : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
        if (mLastRegistrationStats.rat != newRat) {
            mLastRegistrationStats.rat = newRat;
            ratChanged = true;
        }
        mLastRegistrationStats.isIwlanCrossSim = radioTech == REGISTRATION_TECH_CROSS_SIM;

        boolean voiceAvailableNow = capabilities.isCapable(CAPABILITY_TYPE_VOICE);
        boolean voiceAvailabilityChanged =
                (mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) != voiceAvailableNow);
        mLastAvailableFeatures = capabilities;

        // Notify voice RAT change if 1. RAT changed while voice over IMS is available, or 2. voice
        // over IMS availability changed
        if ((ratChanged && voiceAvailableNow) || voiceAvailabilityChanged) {
            mPhone.getDefaultPhone().getServiceStateTracker().getServiceStateStats()
                    .onImsVoiceRegistrationChanged();
        }
    }

    /** Updates the stats when capable features changed. */
    public synchronized void onSetFeatureResponse(
            @MmTelCapability int feature, @ImsRegistrationTech int network, int value) {
        MmTelCapabilities lastCapableFeatures = getLastCapableFeaturesForTech(network);
        if (lastCapableFeatures != null) {
            conclude();
            if (value == ProvisioningManager.PROVISIONING_VALUE_ENABLED) {
                lastCapableFeatures.addCapabilities(feature);
            } else {
                lastCapableFeatures.removeCapabilities(feature);
            }
        }
    }

    /** Updates the stats when IMS registration is progressing. */
    public synchronized void onImsRegistering(@TransportType int imsRadioTech) {
        conclude();

        mLastTransportType = imsRadioTech;
        updateImsRegistrationStats();
        mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech);
        mLastRegistrationState = REGISTRATION_STATE_REGISTERING;
    }

    /** Updates the stats when IMS registration succeeds. */
    public synchronized void onImsRegistered(ImsRegistrationAttributes attributes) {
        // Updates registered_times as soon as the UE is registered
        if (mLastRegistrationState != REGISTRATION_STATE_REGISTERED) {
            // RegistrationStats captures in every state. Changing REGISTERED state has to capture
            // only once.
            mLastRegistrationStats.registeredTimes = 1;
        }

        conclude();

        mLastTransportType = attributes.getTransportType();
        // NOTE: status can be unregistered (no registering phase)
        if (mLastRegistrationState == REGISTRATION_STATE_NOT_REGISTERED) {
            updateImsRegistrationStats();
        }

        mLastRegistrationStats.rat =
                convertTransportTypeToNetworkType(attributes.getTransportType());
        mLastRegistrationStats.isIwlanCrossSim = attributes.getRegistrationTechnology()
                == REGISTRATION_TECH_CROSS_SIM;
        mLastRegistrationState = REGISTRATION_STATE_REGISTERED;
    }

    /** Updates the stats and generates a termination atom when IMS registration fails/ends. */
    public synchronized void onImsUnregistered(ImsReasonInfo reasonInfo) {
        conclude();

        // Generate end reason atom.
        ImsRegistrationTermination termination = new ImsRegistrationTermination();
        if (mLastRegistrationState != REGISTRATION_STATE_NOT_REGISTERED) {
            termination.carrierId = mLastRegistrationStats.carrierId;
            termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat);
            termination.isIwlanCrossSim = mLastRegistrationStats.isIwlanCrossSim;
        } else {
            // if the registration state is from unregistered to unregistered.
            termination.carrierId = mPhone.getDefaultPhone().getCarrierId();
        }
        termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat);
        termination.isMultiSim = SimSlotState.isMultiSim();
        termination.setupFailed = (mLastRegistrationState != REGISTRATION_STATE_REGISTERED);
        termination.reasonCode = reasonInfo.getCode();
        termination.extraCode = reasonInfo.getExtraCode();
        termination.extraMessage = filterExtraMessage(reasonInfo.getExtraMessage());
        termination.count = 1;
        mStorage.addImsRegistrationTermination(termination);

        // Reset state to unregistered.
        mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
        mLastAvailableFeatures = new MmTelCapabilities();
    }

    /** Updates the RAT when service state changes. */
    public synchronized void onServiceStateChanged(ServiceState state) {
        conclude();

        @NetworkType int newRat = state.getDataNetworkType();
        MmTelCapabilities lastCapableFeatures = getLastCapableFeaturesForNetworkType(newRat);

        if (lastCapableFeatures != null) {
            mLastRegistrationStats.rat = newRat;
        } else {
            mLastRegistrationStats.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
        }
    }

    /**
     * Returns the current RAT used for IMS voice registration, or {@link
     * TelephonyManager#NETWORK_TYPE_UNKNOWN} if there isn't any.
     */
    @NetworkType
    public synchronized int getImsVoiceRadioTech() {
        if (mLastRegistrationState == REGISTRATION_STATE_NOT_REGISTERED
                || !mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE)) {
            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
        }
        return mLastRegistrationStats.rat;
    }

    @NetworkType
    private int getRatAtEnd(@NetworkType int lastStateRat) {
        return lastStateRat == TelephonyManager.NETWORK_TYPE_IWLAN ? lastStateRat : getWwanPsRat();
    }

    @NetworkType
    private int convertTransportTypeToNetworkType(@TransportType int transportType) {
        switch (transportType) {
            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
                return getWwanPsRat();
            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
                return TelephonyManager.NETWORK_TYPE_IWLAN;
            default:
                return TelephonyManager.NETWORK_TYPE_UNKNOWN;
        }
    }

    @NetworkType
    private int getWwanPsRat() {
        return ServiceStateStats.getRat(
                mPhone.getServiceStateTracker().getServiceState(),
                NetworkRegistrationInfo.DOMAIN_PS);
    }

    private ImsRegistrationStats getDefaultImsRegistrationStats() {
        Phone phone = mPhone.getDefaultPhone();
        ImsRegistrationStats stats = new ImsRegistrationStats();
        stats.rat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
        return stats;
    }

    private void updateImsRegistrationStats() {
        Phone phone = mPhone.getDefaultPhone();
        mLastRegistrationStats.carrierId = phone.getCarrierId();
        mLastRegistrationStats.simSlotIndex = phone.getPhoneId();
    }

    @Nullable
    private MmTelCapabilities getLastCapableFeaturesForTech(@ImsRegistrationTech int radioTech) {
        switch (radioTech) {
            case REGISTRATION_TECH_NONE:
                return null;
            case REGISTRATION_TECH_IWLAN:
            case REGISTRATION_TECH_CROSS_SIM:
                return mLastWlanCapableFeatures;
            default:
                return mLastWwanCapableFeatures;
        }
    }

    @Nullable
    private MmTelCapabilities getLastCapableFeaturesForNetworkType(@NetworkType int netType) {
        switch (netType) {
            case TelephonyManager.NETWORK_TYPE_UNKNOWN:
                return null;
            case TelephonyManager.NETWORK_TYPE_IWLAN:
                return mLastWlanCapableFeatures;
            default:
                return mLastWwanCapableFeatures;
        }
    }

    @NetworkType
    private int convertRegistrationTechToNetworkType(@ImsRegistrationTech int radioTech) {
        switch (radioTech) {
            case REGISTRATION_TECH_NONE:
                return TelephonyManager.NETWORK_TYPE_UNKNOWN;
            case REGISTRATION_TECH_LTE:
                return TelephonyManager.NETWORK_TYPE_LTE;
            case REGISTRATION_TECH_IWLAN:
            case REGISTRATION_TECH_CROSS_SIM:
                return TelephonyManager.NETWORK_TYPE_IWLAN;
            case REGISTRATION_TECH_NR:
                return TelephonyManager.NETWORK_TYPE_NR;
            default:
                loge("convertRegistrationTechToNetworkType: unknown radio tech %d", radioTech);
                return getWwanPsRat();
        }
    }

    private static ImsRegistrationStats copyOfDimensionsOnly(ImsRegistrationStats source) {
        ImsRegistrationStats dest = new ImsRegistrationStats();

        dest.carrierId = source.carrierId;
        dest.simSlotIndex = source.simSlotIndex;
        dest.rat = source.rat;
        dest.isIwlanCrossSim = source.isIwlanCrossSim;

        return dest;
    }

    @VisibleForTesting
    protected long getTimeMillis() {
        return SystemClock.elapsedRealtime();
    }

    /** Filters IMS extra messages to ensure length limit and remove IDs. */
    public static String filterExtraMessage(@Nullable String str) {
        if (str == null) {
            return "";
        }

        str = PATTERN_UUID.matcher(str).replaceAll(REPLACEMENT_UUID);
        str = PATTERN_URI.matcher(str).replaceAll(REPLACEMENT_URI);
        str = PATTERN_HOSTNAME.matcher(str).replaceAll(REPLACEMENT_HOSTNAME);
        str = PATTERN_IPV4.matcher(str).replaceAll(REPLACEMENT_IPV4);
        str = PATTERN_IPV6.matcher(str).replaceAll(REPLACEMENT_IPV6);
        str = PATTERN_IMEI.matcher(str).replaceAll(REPLACEMENT_IMEI);
        str = PATTERN_UNSEGMENTED_IMEI_IMSI.matcher(str)
                .replaceAll(REPLACEMENT_UNSEGMENTED_IMEI_IMSI);
        str = PATTERN_UNKNOWN_ID.matcher(str).replaceAll(REPLACEMENT_UNKNOWN_ID);

        return str.length() > MAX_EXTRA_MESSAGE_LENGTH
                ? str.substring(0, MAX_EXTRA_MESSAGE_LENGTH)
                : str;
    }

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

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