blob: da350c391ae8747251a6d1c0abe42e5176c82d30 [file] [log] [blame]
/*
* 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 android.annotation.Nullable;
import android.os.SystemClock;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.NetworkType;
import android.telephony.DataFailCause;
import android.telephony.ServiceState;
import android.telephony.ServiceState.RilRadioTechnology;
import android.telephony.TelephonyManager;
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.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
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.telephony.Rlog;
import java.util.Random;
/** Collects data call change events per DataConnection for the pulled atom. */
public class DataCallSessionStats {
private static final String TAG = DataCallSessionStats.class.getSimpleName();
private final Phone mPhone;
private long mStartTime;
@Nullable private DataCallSession mDataCallSession;
private final PersistAtomsStorage mAtomsStorage =
PhoneFactory.getMetricsCollector().getAtomsStorage();
private static final Random RANDOM = new Random();
public DataCallSessionStats(Phone phone) {
mPhone = phone;
}
/** Creates a new ongoing atom when data call is set up. */
public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) {
mDataCallSession = getDefaultProto(apnTypeBitMask);
mStartTime = getTimeMillis();
}
/**
* Updates the ongoing dataCall's atom for data call response event.
*
* @param response setup Data call response
* @param radioTechnology The data call RAT
* @param apnTypeBitmask APN type bitmask
* @param protocol Data connection protocol
* @param failureCause failure cause as per android.telephony.DataFailCause
*/
public synchronized void onSetupDataCallResponse(
@Nullable DataCallResponse response,
@RilRadioTechnology int radioTechnology,
@ApnType int apnTypeBitmask,
@ProtocolType int protocol,
@DataFailureCause int failureCause) {
// there should've been a call to onSetupDataCall to initiate the atom,
// so this method is being called out of order -> no metric will be logged
if (mDataCallSession == null) {
loge("onSetupDataCallResponse: no DataCallSession atom has been initiated.");
return;
}
mDataCallSession.ratAtEnd = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology);
// only set if apn hasn't been set during setup
if (mDataCallSession.apnTypeBitmask == 0) {
mDataCallSession.apnTypeBitmask = apnTypeBitmask;
}
mDataCallSession.ipType = protocol;
mDataCallSession.failureCause = failureCause;
if (response != null) {
mDataCallSession.suggestedRetryMillis =
(int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE);
// If setup has failed, then store the atom
if (failureCause != DataFailCause.NONE) {
mDataCallSession.failureCause = failureCause;
mDataCallSession.setupFailed = true;
mDataCallSession.ongoing = false;
mAtomsStorage.addDataCallSession(mDataCallSession);
mDataCallSession = null;
}
}
}
/**
* Updates the dataCall atom when data call is deactivated.
*
* @param reason Deactivate reason
*/
public synchronized 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 (mDataCallSession == null) {
loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated.");
return;
}
switch (reason) {
case DataService.REQUEST_REASON_NORMAL:
mDataCallSession.deactivateReason =
DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL;
break;
case DataService.REQUEST_REASON_SHUTDOWN:
mDataCallSession.deactivateReason =
DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF;
break;
case DataService.REQUEST_REASON_HANDOVER:
mDataCallSession.deactivateReason =
DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER;
break;
default:
mDataCallSession.deactivateReason =
DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN;
break;
}
}
/** Stores the atom when DataConnection reaches DISCONNECTED state.
* @param failureCause failure cause as per android.telephony.DataFailCause
**/
public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) {
// 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 (mDataCallSession == null) {
loge("onDataCallDisconnected: no DataCallSession atom has been initiated.");
return;
}
mDataCallSession.failureCause = failureCause;
mDataCallSession.oosAtEnd = getIsOos();
mDataCallSession.ongoing = false;
mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime);
// store for the data call list event, after DataCall is disconnected and entered into
// inactive mode
mAtomsStorage.addDataCallSession(mDataCallSession);
mDataCallSession = null;
}
/**
* Updates the atom when data registration state or RAT changes.
*
* <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data
* registration state change.
*/
public synchronized void onDrsOrRatChanged(@RilRadioTechnology int radioTechnology) {
@NetworkType int currentRat =
ServiceState.rilRadioTechnologyToNetworkType(radioTechnology);
if (mDataCallSession != null
&& currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN
&& mDataCallSession.ratAtEnd != currentRat) {
mDataCallSession.ratSwitchCount++;
mDataCallSession.ratAtEnd = currentRat;
mDataCallSession.bandAtEnd = ServiceStateStats.getBand(mPhone, currentRat);
}
}
private static long convertMillisToMinutes(long millis) {
return Math.round(millis / 60000.0);
}
/** Creates a proto for a normal {@code DataCallSession} with default values. */
private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) {
DataCallSession proto = new DataCallSession();
proto.dimension = RANDOM.nextInt();
proto.isMultiSim = SimSlotState.isMultiSim();
proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId());
proto.apnTypeBitmask = apnTypeBitmask;
proto.carrierId = mPhone.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 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 boolean getIsOos() {
ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
ServiceState serviceState =
serviceStateTracker != null ? serviceStateTracker.getServiceState() : null;
return serviceState != null
? serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE
: false;
}
private void loge(String format, Object... args) {
Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
}
@VisibleForTesting
protected long getTimeMillis() {
return SystemClock.elapsedRealtime();
}
}