1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.metrics; 18 19 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; 20 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; 21 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; 22 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 23 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; 24 25 import android.annotation.Nullable; 26 import android.os.SystemClock; 27 import android.telephony.Annotation.ApnType; 28 import android.telephony.Annotation.DataFailureCause; 29 import android.telephony.Annotation.NetworkType; 30 import android.telephony.DataFailCause; 31 import android.telephony.ServiceState; 32 import android.telephony.ServiceState.RilRadioTechnology; 33 import android.telephony.TelephonyManager; 34 import android.telephony.data.ApnSetting.ProtocolType; 35 import android.telephony.data.DataCallResponse; 36 import android.telephony.data.DataService; 37 import android.telephony.data.DataService.DeactivateDataReason; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.telephony.Phone; 41 import com.android.internal.telephony.PhoneFactory; 42 import com.android.internal.telephony.ServiceStateTracker; 43 import com.android.internal.telephony.SubscriptionController; 44 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; 45 import com.android.telephony.Rlog; 46 47 import java.util.Random; 48 49 /** Collects data call change events per DataConnection for the pulled atom. */ 50 public class DataCallSessionStats { 51 private static final String TAG = DataCallSessionStats.class.getSimpleName(); 52 53 private final Phone mPhone; 54 private long mStartTime; 55 @Nullable private DataCallSession mDataCallSession; 56 57 private final PersistAtomsStorage mAtomsStorage = 58 PhoneFactory.getMetricsCollector().getAtomsStorage(); 59 60 private static final Random RANDOM = new Random(); 61 DataCallSessionStats(Phone phone)62 public DataCallSessionStats(Phone phone) { 63 mPhone = phone; 64 } 65 66 /** Creates a new ongoing atom when data call is set up. */ onSetupDataCall(@pnType int apnTypeBitMask)67 public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) { 68 mDataCallSession = getDefaultProto(apnTypeBitMask); 69 mStartTime = getTimeMillis(); 70 } 71 72 /** 73 * Updates the ongoing dataCall's atom for data call response event. 74 * 75 * @param response setup Data call response 76 * @param radioTechnology The data call RAT 77 * @param apnTypeBitmask APN type bitmask 78 * @param protocol Data connection protocol 79 * @param failureCause failure cause as per android.telephony.DataFailCause 80 */ onSetupDataCallResponse( @ullable DataCallResponse response, @RilRadioTechnology int radioTechnology, @ApnType int apnTypeBitmask, @ProtocolType int protocol, @DataFailureCause int failureCause)81 public synchronized void onSetupDataCallResponse( 82 @Nullable DataCallResponse response, 83 @RilRadioTechnology int radioTechnology, 84 @ApnType int apnTypeBitmask, 85 @ProtocolType int protocol, 86 @DataFailureCause int failureCause) { 87 // there should've been a call to onSetupDataCall to initiate the atom, 88 // so this method is being called out of order -> no metric will be logged 89 if (mDataCallSession == null) { 90 loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); 91 return; 92 } 93 mDataCallSession.ratAtEnd = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology); 94 95 // only set if apn hasn't been set during setup 96 if (mDataCallSession.apnTypeBitmask == 0) { 97 mDataCallSession.apnTypeBitmask = apnTypeBitmask; 98 } 99 100 mDataCallSession.ipType = protocol; 101 mDataCallSession.failureCause = failureCause; 102 if (response != null) { 103 mDataCallSession.suggestedRetryMillis = 104 (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE); 105 // If setup has failed, then store the atom 106 if (failureCause != DataFailCause.NONE) { 107 mDataCallSession.failureCause = failureCause; 108 mDataCallSession.setupFailed = true; 109 mDataCallSession.ongoing = false; 110 mAtomsStorage.addDataCallSession(mDataCallSession); 111 mDataCallSession = null; 112 } 113 } 114 } 115 116 /** 117 * Updates the dataCall atom when data call is deactivated. 118 * 119 * @param reason Deactivate reason 120 */ setDeactivateDataCallReason(@eactivateDataReason int reason)121 public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) { 122 // there should've been another call to initiate the atom, 123 // so this method is being called out of order -> no metric will be logged 124 if (mDataCallSession == null) { 125 loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated."); 126 return; 127 } 128 switch (reason) { 129 case DataService.REQUEST_REASON_NORMAL: 130 mDataCallSession.deactivateReason = 131 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; 132 break; 133 case DataService.REQUEST_REASON_SHUTDOWN: 134 mDataCallSession.deactivateReason = 135 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; 136 break; 137 case DataService.REQUEST_REASON_HANDOVER: 138 mDataCallSession.deactivateReason = 139 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; 140 break; 141 default: 142 mDataCallSession.deactivateReason = 143 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 144 break; 145 } 146 } 147 148 /** Stores the atom when DataConnection reaches DISCONNECTED state. 149 * @param failureCause failure cause as per android.telephony.DataFailCause 150 **/ onDataCallDisconnected(@ataFailureCause int failureCause)151 public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) { 152 // there should've been another call to initiate the atom, 153 // so this method is being called out of order -> no atom will be saved 154 if (mDataCallSession == null) { 155 loge("onDataCallDisconnected: no DataCallSession atom has been initiated."); 156 return; 157 } 158 mDataCallSession.failureCause = failureCause; 159 mDataCallSession.oosAtEnd = getIsOos(); 160 mDataCallSession.ongoing = false; 161 mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime); 162 // store for the data call list event, after DataCall is disconnected and entered into 163 // inactive mode 164 mAtomsStorage.addDataCallSession(mDataCallSession); 165 mDataCallSession = null; 166 } 167 168 /** 169 * Updates the atom when data registration state or RAT changes. 170 * 171 * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data 172 * registration state change. 173 */ onDrsOrRatChanged(@ilRadioTechnology int radioTechnology)174 public synchronized void onDrsOrRatChanged(@RilRadioTechnology int radioTechnology) { 175 @NetworkType int currentRat = 176 ServiceState.rilRadioTechnologyToNetworkType(radioTechnology); 177 if (mDataCallSession != null 178 && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN 179 && mDataCallSession.ratAtEnd != currentRat) { 180 mDataCallSession.ratSwitchCount++; 181 mDataCallSession.ratAtEnd = currentRat; 182 mDataCallSession.bandAtEnd = ServiceStateStats.getBand(mPhone, currentRat); 183 } 184 } 185 convertMillisToMinutes(long millis)186 private static long convertMillisToMinutes(long millis) { 187 return Math.round(millis / 60000.0); 188 } 189 190 /** Creates a proto for a normal {@code DataCallSession} with default values. */ getDefaultProto(@pnType int apnTypeBitmask)191 private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) { 192 DataCallSession proto = new DataCallSession(); 193 proto.dimension = RANDOM.nextInt(); 194 proto.isMultiSim = SimSlotState.isMultiSim(); 195 proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId()); 196 proto.apnTypeBitmask = apnTypeBitmask; 197 proto.carrierId = mPhone.getCarrierId(); 198 proto.isRoaming = getIsRoaming(); 199 proto.oosAtEnd = false; 200 proto.ratSwitchCount = 0L; 201 proto.isOpportunistic = getIsOpportunistic(); 202 proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; 203 proto.setupFailed = false; 204 proto.failureCause = DataFailCause.NONE; 205 proto.suggestedRetryMillis = 0; 206 proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 207 proto.durationMinutes = 0; 208 proto.ongoing = true; 209 return proto; 210 } 211 getIsRoaming()212 private boolean getIsRoaming() { 213 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 214 ServiceState serviceState = 215 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 216 return serviceState != null ? serviceState.getRoaming() : false; 217 } 218 getIsOpportunistic()219 private boolean getIsOpportunistic() { 220 SubscriptionController subController = SubscriptionController.getInstance(); 221 return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false; 222 } 223 getIsOos()224 private boolean getIsOos() { 225 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 226 ServiceState serviceState = 227 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 228 return serviceState != null 229 ? serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE 230 : false; 231 } 232 loge(String format, Object... args)233 private void loge(String format, Object... args) { 234 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 235 } 236 237 @VisibleForTesting getTimeMillis()238 protected long getTimeMillis() { 239 return SystemClock.elapsedRealtime(); 240 } 241 } 242