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

Commit 5af04df6 authored by Nazanin Bakhshi's avatar Nazanin Bakhshi Committed by Android (Google) Code Review
Browse files

Merge "Add support for Data calls metrics"

parents ace0b044 b3440563
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
@@ -81,6 +81,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;
@@ -190,6 +191,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.
     */
@@ -663,6 +667,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);
@@ -866,6 +871,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) {
@@ -1802,6 +1808,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) {
@@ -1824,6 +1832,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;
                default:
                    if (DBG) {
@@ -2052,6 +2064,7 @@ public class DataConnection extends StateMachine {
                    .registerCarrierPrivilegesListener(
                            getHandler(), EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED, null);
            notifyDataConnectionState();
            mDataCallSessionStats.onSetupDataCall();
        }
        @Override
        public boolean processMessage(Message msg) {
@@ -2142,6 +2155,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;
@@ -2289,8 +2305,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
@@ -2318,6 +2335,7 @@ public class DataConnection extends StateMachine {

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

            mPhone.getCarrierPrivilegesTracker().unregisterCarrierPrivilegesListener(getHandler());
        }
@@ -2429,6 +2447,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:
@@ -2945,6 +2967,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
@@ -113,6 +113,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;
@@ -329,6 +330,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;

@@ -4683,7 +4687,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;
@@ -4785,6 +4789,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