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

Commit 3bb1c983 authored by Nazanin's avatar Nazanin Committed by Chi Zhang
Browse files

Add support for Data calls metrics

Bug: 167453031
Test: manual testing
Merged-In: I84606557a00091b7a2f44f6a03df2e316a7b46dd
Change-Id: I5512e67055914fa59fa44cb9e50c4e860b747a61
parent 34321b6b
Loading
Loading
Loading
Loading
+28 −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: 11
// Next id: 13
message PersistAtoms {
    /* Aggregated RAT usage during the call. */
    repeated RawVoiceCallRatUsage raw_voice_call_rat_usage = 1;
@@ -54,6 +54,12 @@ message PersistAtoms {

    /* Last version of carrier ID table sent. */
    optional int32 carrier_id_table_version = 10;

    /* Data Call session statistics and information. */
    repeated DataCallSession data_call_session = 11;

    /* Timestamp of last data_call_session pull. */
    optional int64 data_call_session_pull_timestamp_millis = 12;
}

// The canonical versions of the following enums live in:
@@ -139,3 +145,24 @@ message CarrierIdMismatch {
    optional string gid1 = 2;
    optional string spn = 3;
}

message DataCallSession {
    optional int32 dimension = 1;
    optional bool is_multi_sim = 2;
    optional bool is_esim = 3;
    optional int32 profile = 4;
    optional int32 apn_type_bitmask = 5;
    optional int32 carrier_id = 6;
    optional bool is_roaming = 7;
    optional int32 rat_at_end = 8;
    optional bool oos_at_end = 9;
    optional int64 rat_switch_count = 10;
    optional bool is_opportunistic = 11;
    optional int32 ip_type = 12;
    optional bool setup_failed = 13;
    optional int32 failure_cause = 14;
    optional int32 suggested_retry_millis = 15;
    optional int32 deactivate_reason = 16;
    optional int64 duration_minutes = 17;
    optional bool ongoing = 18;
}
+29 −1
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.internal.telephony.dataconnection.DcTracker.ReleaseNetworkType;
import com.android.internal.telephony.dataconnection.DcTracker.RequestNetworkType;
import com.android.internal.telephony.metrics.DataCallSessionStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.nano.TelephonyProto.RilDataCall;
import com.android.internal.util.AsyncChannel;
@@ -196,6 +197,9 @@ public class DataConnection extends StateMachine {

    private int[] mAdministratorUids = new int[0];

    // stats per data call
    private DataCallSessionStats mDataCallSessionStats;

    /**
     * Used internally for saving connecting parameters.
     */
@@ -690,6 +694,7 @@ public class DataConnection extends StateMachine {
        mCid = -1;
        mDataRegState = mPhone.getServiceState().getDataRegistrationState();
        mIsSuspended = false;
        mDataCallSessionStats = new DataCallSessionStats(mPhone);

        int networkType = getNetworkType();
        mRilRat = ServiceState.networkTypeToRilRadioTechnology(networkType);
@@ -1015,6 +1020,7 @@ public class DataConnection extends StateMachine {
        if (apnContext != null) apnContext.requestLog(str);
        mDataServiceManager.deactivateDataCall(mCid, discReason,
                obtainMessage(EVENT_DEACTIVATE_DONE, mTag, 0, o));
        mDataCallSessionStats.setDeactivateDataCallReason(discReason);
    }

    private void notifyAllWithEvent(ApnContext alreadySent, int event, String reason) {
@@ -1961,6 +1967,8 @@ public class DataConnection extends StateMachine {
                    if (DBG) log("DcDefaultState EVENT_TEAR_DOWN_NOW");
                    mDataServiceManager.deactivateDataCall(mCid, DataService.REQUEST_REASON_NORMAL,
                            null);
                    mDataCallSessionStats.setDeactivateDataCallReason(
                            DataService.REQUEST_REASON_NORMAL);
                    break;
                case EVENT_LOST_CONNECTION:
                    if (DBG) {
@@ -1983,6 +1991,10 @@ public class DataConnection extends StateMachine {
                                + " drs=" + mDataRegState
                                + " mRilRat=" + mRilRat);
                    }
                    // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed
                    if (mRilRat != 0) {
                        mDataCallSessionStats.onRatChanged(mRilRat);
                    }
                    break;

                case EVENT_START_HANDOVER:  //calls startHandover()
@@ -2235,6 +2247,7 @@ public class DataConnection extends StateMachine {
                    .registerCarrierPrivilegesListener(
                            getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null);
            notifyDataConnectionState();
            mDataCallSessionStats.onSetupDataCall();
        }
        @Override
        public boolean processMessage(Message msg) {
@@ -2332,6 +2345,9 @@ public class DataConnection extends StateMachine {
                            throw new RuntimeException("Unknown SetupResult, should not happen");
                    }
                    retVal = HANDLED;
                    mDataCallSessionStats
                            .onSetupDataCallResponse(dataCallResponse, cp.mRilRat, cp.mProfileId,
                                    mApnSetting.getApnTypeBitmask(), mApnSetting.getProtocol());
                    break;
                case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
                    AsyncResult asyncResult = (AsyncResult) msg.obj;
@@ -2484,8 +2500,9 @@ public class DataConnection extends StateMachine {
                        getHandler(), DataConnection.EVENT_LINK_CAPACITY_CHANGED, null);
            }
            notifyDataConnectionState();
            int apnBitMask = mApnSetting.getApnTypeBitmask();
            TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
                    mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.CONNECTED);
                    mCid, apnBitMask, RilDataCall.State.CONNECTED);
        }

        @Override
@@ -2513,6 +2530,7 @@ public class DataConnection extends StateMachine {

            TelephonyMetrics.getInstance().writeRilDataCallEvent(mPhone.getPhoneId(),
                    mCid, mApnSetting.getApnTypeBitmask(), RilDataCall.State.DISCONNECTED);
            mDataCallSessionStats.onDataCallDisconnected(mCid);

            mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler());
        }
@@ -2624,6 +2642,10 @@ public class DataConnection extends StateMachine {
                        mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
                    }
                    retVal = HANDLED;
                    // this is for DRS or RAT changes, so only call onRatChanged if RAT is changed
                    if (mRilRat != 0) {
                        mDataCallSessionStats.onRatChanged(mRilRat);
                    }
                    break;
                }
                case EVENT_NR_FREQUENCY_CHANGED:
@@ -3161,6 +3183,12 @@ public class DataConnection extends StateMachine {
        }
    }

    /** Sets the {@link DataCallSessionStats} mock for this phone ID during unit testing. */
    @VisibleForTesting
    public void setDataCallSessionStats(DataCallSessionStats dataCallSessionStats) {
        mDataCallSessionStats = dataCallSessionStats;
    }

    /**
     * @return the string for msg.what as our info.
     */
+6 −1
Original line number Diff line number Diff line
@@ -114,6 +114,7 @@ import com.android.internal.telephony.SubscriptionInfoUpdater;
import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataAllowedReasonType;
import com.android.internal.telephony.dataconnection.DataConnectionReasons.DataDisallowedReasonType;
import com.android.internal.telephony.dataconnection.DataEnabledSettings.DataEnabledChangedReason;
import com.android.internal.telephony.metrics.DataStallRecoveryStats;
import com.android.internal.telephony.metrics.TelephonyMetrics;
import com.android.internal.telephony.util.ArrayUtils;
import com.android.internal.telephony.util.TelephonyUtils;
@@ -340,6 +341,9 @@ public class DcTracker extends Handler {
    private boolean mNrSaSub6Unmetered = false;
    private boolean mRoamingUnmetered = false;

    // stats per data call recovery event
    private DataStallRecoveryStats mDataStallRecoveryStats;

    /* List of SubscriptionPlans, updated when initialized and when plans are changed. */
    private List<SubscriptionPlan> mSubscriptionPlans = null;

@@ -4704,7 +4708,7 @@ public class DcTracker extends Handler {
            RECOVERY_ACTION_RADIO_RESTART
        })
    @Retention(RetentionPolicy.SOURCE)
    private @interface RecoveryAction {};
    public @interface RecoveryAction {};
    private static final int RECOVERY_ACTION_GET_DATA_CALL_LIST      = 0;
    private static final int RECOVERY_ACTION_CLEANUP                 = 1;
    private static final int RECOVERY_ACTION_REREGISTER              = 2;
@@ -4806,6 +4810,7 @@ public class DcTracker extends Handler {
                        mPhone.getPhoneId(), signalStrength);
                TelephonyMetrics.getInstance().writeDataStallEvent(
                        mPhone.getPhoneId(), recoveryAction);
                DataStallRecoveryStats.onDataStallEvent(recoveryAction, mPhone);
                broadcastDataStallDetected(recoveryAction);

                switch (recoveryAction) {
+253 −0
Original line number Diff line number Diff line
/*
 * 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.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT;

import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.NetworkType;
import android.telephony.DataFailCause;
import android.telephony.ServiceState;
import android.telephony.data.ApnSetting;
import android.telephony.data.ApnSetting.ProtocolType;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataService;
import android.telephony.data.DataService.DeactivateDataReason;

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.SubscriptionController;
import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.telephony.uicc.UiccSlot;
import com.android.telephony.Rlog;

import java.util.Random;

/** Collects data call change events per DcTracker for the pulled atom. */
public class DataCallSessionStats {
    private static final String TAG = DataCallSessionStats.class.getSimpleName();

    private final Phone mPhone;
    private long mStartTime;
    DataCallSession mOngoingDataCall;

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

    private static final Random RANDOM = new Random();

    public DataCallSessionStats(Phone phone) {
        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            mPhone = phone.getDefaultPhone();
        } else {
            mPhone = phone;
        }
    }

    /**
     * create a new ongoing atom when data cal is set up
     */
    public synchronized void onSetupDataCall() {
        // there shouldn't be an ongoing dataCall here, if that's the case, it means that
        // deactivateDataCall hasn't been processed properly, so we save the previous atom here
        // and move on.
        if (mOngoingDataCall != null && mOngoingDataCall.ongoing) {
            mOngoingDataCall.failureCause = DataFailCause.UNKNOWN;
            mOngoingDataCall.durationMinutes =
                    convertMillisToMinutes(System.currentTimeMillis() - mStartTime);
            mOngoingDataCall.ongoing = false;
            mAtomsStorage.addDataCallSession(mOngoingDataCall);
        }
        mOngoingDataCall = getDefaultProto();
    }

    /**
     * update the ongoing dataCall's atom for data call response event
     *
     * @param response setup Data call response
     * @param radioTechnology The data call RAT
     * @param profileId Data profile id
     * @param apnTypeBitmask APN type bitmask
     * @param protocol Data connection protocol
     */
    public synchronized void onSetupDataCallResponse(DataCallResponse response,
            @NetworkType int radioTechnology, int profileId,
            @ApnType int apnTypeBitmask, @ProtocolType int protocol) {
        // there should've been another call to initiate the atom,
        // so this method is being called out of order -> no metric will be logged
        if (mOngoingDataCall == null) {
            loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
            return;
        }
        mOngoingDataCall.ratAtEnd =
                ServiceState.rilRadioTechnologyToAccessNetworkType(radioTechnology);
        mOngoingDataCall.profile = profileId;
        mOngoingDataCall.apnTypeBitmask = apnTypeBitmask;
        mOngoingDataCall.ipType = protocol;
        mStartTime = System.currentTimeMillis();
        if (response != null) {
            if (response.getCause() == 0) {
                mOngoingDataCall.failureCause = DataFailCause.NONE;
            } else {
                mOngoingDataCall.failureCause = response.getCause();
                mOngoingDataCall.setupFailed = true;
                // set dataCall as inactive
                mOngoingDataCall.ongoing = false;
                // store it only if setup has failed
                mAtomsStorage.addDataCallSession(mOngoingDataCall);
            }
            mOngoingDataCall.suggestedRetryMillis = response.getSuggestedRetryTime();
        }
    }

    /**
     * update the ongoing dataCall's atom when data call is deactivated
     *
     * @param reason Deactivate reason
     */
    public void setDeactivateDataCallReason(@DeactivateDataReason int reason) {
        // there should've been another call to initiate the atom,
        // so this method is being called out of order -> no metric will be logged
        if (mOngoingDataCall == null) {
            loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
            return;
        }
        switch (reason) {
            case DataService.REQUEST_REASON_NORMAL:
                mOngoingDataCall.deactivateReason =
                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
                break;
            case DataService.REQUEST_REASON_SHUTDOWN:
                mOngoingDataCall.deactivateReason =
                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
                break;
            case DataService.REQUEST_REASON_HANDOVER:
                mOngoingDataCall.deactivateReason =
                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
                break;
            default:
                mOngoingDataCall.deactivateReason =
                        DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
                mOngoingDataCall.oosAtEnd = true;
        }
    }

    /**
     * store the atom, when DataConnection reaches DISCONNECTED state
     * @param cid              Context Id, uniquely identifies the call
     */
    public void onDataCallDisconnected(int cid) {
        // there should've been another call to initiate the atom,
        // so this method is being called out of order -> no atom will be saved
        if (mOngoingDataCall == null) {
            loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
            return;
        }
        mOngoingDataCall.carrierId = cid;
        mOngoingDataCall.ongoing = false;
        mOngoingDataCall.durationMinutes =
                convertMillisToMinutes(System.currentTimeMillis() - mStartTime);
        // store for the data call list event, after DataCall is disconnected and entered into
        // inactive mode
        mAtomsStorage.addDataCallSession(mOngoingDataCall);
    }

    /** Updates this RAT when it changes. */
    public synchronized void onRatChanged(@NetworkType int rat) {
        // if no data call is initiated, or we have a new data call while the last one has ended
        // because onRatChanged might be called before onSetupDataCall
        if (mOngoingDataCall == null || !mOngoingDataCall.ongoing) {
            mOngoingDataCall = getDefaultProto();
        }
        if (mOngoingDataCall.ratAtEnd != rat) {
            mOngoingDataCall.ratSwitchCount++;
            mOngoingDataCall.ratAtEnd = rat;
        }
    }

    private static long convertMillisToMinutes(long millis) {
        return Math.round(millis / 60000);
    }

    /** Creates a proto for a normal {@code DataCallSession} with default values. */
    private DataCallSession getDefaultProto() {
        DataCallSession proto = new DataCallSession();
        proto.dimension = RANDOM.nextInt();
        proto.isMultiSim = isMultiSim();
        proto.isEsim = isEsim();
        proto.profile = DATA_CALL_SESSION__PROFILE__DATA_PROFILE_DEFAULT;
        proto.apnTypeBitmask = ApnSetting.TYPE_NONE;
        proto.carrierId = getCarrierId();
        proto.isRoaming = getIsRoaming();
        proto.oosAtEnd = false;
        proto.ratSwitchCount = 0L;
        proto.isOpportunistic = getIsOpportunistic();
        proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4;
        proto.setupFailed = false;
        proto.failureCause = DataFailCause.NONE;
        proto.suggestedRetryMillis = 0;
        proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
        proto.durationMinutes = 0;
        proto.ongoing = true;
        return proto;
    }

    private boolean isMultiSim() {
        return SimSlotState.getCurrentState().numActiveSims > 1;
    }

    private boolean isEsim() {
        UiccController uiccController = UiccController.getInstance();
        int slotId = uiccController.getSlotIdFromPhoneId(mPhone.getPhoneId());
        UiccSlot slot = uiccController.getUiccSlot(slotId);
        if (slot != null) {
            return slot.isEuicc();
        } else {
            // should not happen, but assume we are not using eSIM
            loge("isEsim: slot %d is null", slotId);
            return false;
        }
    }

    private boolean getIsRoaming() {
        ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
        ServiceState serviceState =
                serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
        return serviceState != null ? serviceState.getRoaming() : false;
    }

    private boolean getIsOpportunistic() {
        SubscriptionController subController = SubscriptionController.getInstance();
        return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false;
    }

    private int getCarrierId() {
        return mPhone.getCarrierId();
    }

    private void loge(String format, Object... args) {
        Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
    }
}
+67 −0
Original line number Diff line number Diff line
/*
 * 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 android.telephony.Annotation.NetworkType;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;

import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.SubscriptionController;
import com.android.internal.telephony.TelephonyStatsLog;
import com.android.internal.telephony.dataconnection.DcTracker;

/** Generates metrics related to data stall recovery events per phone ID for the pushed atom. */
public class DataStallRecoveryStats {
    /**
     * Create and push new atom when there is a data stall recovery event
     *
     * @param recoveryAction Data stall recovery action
     * @param phone
     */
    public static void onDataStallEvent(@DcTracker.RecoveryAction int recoveryAction,
            Phone phone) {
        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
            phone = phone.getDefaultPhone();
        }

        int carrierId = phone.getCarrierId();
        int rat = getRat(phone);
        // the number returned here matches the SignalStrength enum we have
        int signalStrength = phone.getSignalStrength().getLevel();
        boolean isOpportunistic = getIsOpportunistic(phone);
        boolean isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1;

        TelephonyStatsLog.write(TelephonyStatsLog.DATA_STALL_RECOVERY_REPORTED, carrierId, rat,
                signalStrength, recoveryAction, isOpportunistic, isMultiSim);
    }

    private static @NetworkType int getRat(Phone phone) {
        ServiceStateTracker serviceStateTracker = phone.getServiceStateTracker();
        ServiceState serviceState =
                serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
        return serviceState != null ? serviceState.getVoiceNetworkType()
                : TelephonyManager.NETWORK_TYPE_UNKNOWN;
    }

    private static boolean getIsOpportunistic(Phone phone) {
        SubscriptionController subController = SubscriptionController.getInstance();
        return subController != null ? subController.isOpportunistic(phone.getSubId()) : false;
    }
}
Loading