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 PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this); 71 } 72 73 /** 74 * Updates the ongoing dataCall's atom for data call response event. 75 * 76 * @param response setup Data call response 77 * @param radioTechnology The data call RAT 78 * @param apnTypeBitmask APN type bitmask 79 * @param protocol Data connection protocol 80 * @param failureCause failure cause as per android.telephony.DataFailCause 81 */ onSetupDataCallResponse( @ullable DataCallResponse response, @RilRadioTechnology int radioTechnology, @ApnType int apnTypeBitmask, @ProtocolType int protocol, @DataFailureCause int failureCause)82 public synchronized void onSetupDataCallResponse( 83 @Nullable DataCallResponse response, 84 @RilRadioTechnology int radioTechnology, 85 @ApnType int apnTypeBitmask, 86 @ProtocolType int protocol, 87 @DataFailureCause int failureCause) { 88 // there should've been a call to onSetupDataCall to initiate the atom, 89 // so this method is being called out of order -> no metric will be logged 90 if (mDataCallSession == null) { 91 loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); 92 return; 93 } 94 95 @NetworkType int currentRat = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology); 96 if (currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 97 mDataCallSession.ratAtEnd = currentRat; 98 mDataCallSession.bandAtEnd = 99 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 100 ? 0 101 : ServiceStateStats.getBand(mPhone); 102 } 103 104 // only set if apn hasn't been set during setup 105 if (mDataCallSession.apnTypeBitmask == 0) { 106 mDataCallSession.apnTypeBitmask = apnTypeBitmask; 107 } 108 109 mDataCallSession.ipType = protocol; 110 mDataCallSession.failureCause = failureCause; 111 if (response != null) { 112 mDataCallSession.suggestedRetryMillis = 113 (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE); 114 // If setup has failed, then store the atom 115 if (failureCause != DataFailCause.NONE) { 116 mDataCallSession.failureCause = failureCause; 117 mDataCallSession.oosAtEnd = getIsOos(); 118 mDataCallSession.setupFailed = true; 119 mDataCallSession.ongoing = false; 120 PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this); 121 mAtomsStorage.addDataCallSession(mDataCallSession); 122 mDataCallSession = null; 123 } 124 } 125 } 126 127 /** 128 * Updates the dataCall atom when data call is deactivated. 129 * 130 * @param reason Deactivate reason 131 */ setDeactivateDataCallReason(@eactivateDataReason int reason)132 public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) { 133 // there should've been another call to initiate the atom, 134 // so this method is being called out of order -> no metric will be logged 135 if (mDataCallSession == null) { 136 loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated."); 137 return; 138 } 139 switch (reason) { 140 case DataService.REQUEST_REASON_NORMAL: 141 mDataCallSession.deactivateReason = 142 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; 143 break; 144 case DataService.REQUEST_REASON_SHUTDOWN: 145 mDataCallSession.deactivateReason = 146 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; 147 break; 148 case DataService.REQUEST_REASON_HANDOVER: 149 mDataCallSession.deactivateReason = 150 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; 151 break; 152 default: 153 mDataCallSession.deactivateReason = 154 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 155 break; 156 } 157 } 158 159 /** 160 * Stores the atom when DataConnection reaches DISCONNECTED state. 161 * 162 * @param failureCause failure cause as per android.telephony.DataFailCause 163 */ onDataCallDisconnected(@ataFailureCause int failureCause)164 public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) { 165 // there should've been another call to initiate the atom, 166 // so this method is being called out of order -> no atom will be saved 167 // this also happens when DataConnection is created, which is expected 168 if (mDataCallSession == null) { 169 logi("onDataCallDisconnected: no DataCallSession atom has been initiated."); 170 return; 171 } 172 mDataCallSession.failureCause = failureCause; 173 mDataCallSession.oosAtEnd = getIsOos(); 174 mDataCallSession.ongoing = false; 175 mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime); 176 // store for the data call list event, after DataCall is disconnected and entered into 177 // inactive mode 178 PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this); 179 mAtomsStorage.addDataCallSession(mDataCallSession); 180 mDataCallSession = null; 181 } 182 183 /** 184 * Updates the atom when data registration state or RAT changes. 185 * 186 * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data 187 * registration state change. 188 */ onDrsOrRatChanged(@ilRadioTechnology int radioTechnology)189 public synchronized void onDrsOrRatChanged(@RilRadioTechnology int radioTechnology) { 190 @NetworkType int currentRat = ServiceState.rilRadioTechnologyToNetworkType(radioTechnology); 191 if (mDataCallSession != null && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 192 if (mDataCallSession.ratAtEnd != currentRat) { 193 mDataCallSession.ratSwitchCount++; 194 mDataCallSession.ratAtEnd = currentRat; 195 } 196 // band may have changed even if RAT was the same 197 mDataCallSession.bandAtEnd = 198 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 199 ? 0 200 : ServiceStateStats.getBand(mPhone); 201 } 202 } 203 204 /** Add the on-going data call segment to the atom storage. */ conclude()205 public synchronized void conclude() { 206 if (mDataCallSession != null) { 207 DataCallSession call = copyOf(mDataCallSession); 208 long nowMillis = getTimeMillis(); 209 call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime); 210 mStartTime = nowMillis; 211 mDataCallSession.ratSwitchCount = 0L; 212 mAtomsStorage.addDataCallSession(call); 213 } 214 } 215 convertMillisToMinutes(long millis)216 private static long convertMillisToMinutes(long millis) { 217 return Math.round(millis / 60000.0); 218 } 219 copyOf(DataCallSession call)220 private static DataCallSession copyOf(DataCallSession call) { 221 DataCallSession copy = new DataCallSession(); 222 copy.dimension = call.dimension; 223 copy.isMultiSim = call.isMultiSim; 224 copy.isEsim = call.isEsim; 225 copy.apnTypeBitmask = call.apnTypeBitmask; 226 copy.carrierId = call.carrierId; 227 copy.isRoaming = call.isRoaming; 228 copy.ratAtEnd = call.ratAtEnd; 229 copy.oosAtEnd = call.oosAtEnd; 230 copy.ratSwitchCount = call.ratSwitchCount; 231 copy.isOpportunistic = call.isOpportunistic; 232 copy.ipType = call.ipType; 233 copy.setupFailed = call.setupFailed; 234 copy.failureCause = call.failureCause; 235 copy.suggestedRetryMillis = call.suggestedRetryMillis; 236 copy.deactivateReason = call.deactivateReason; 237 copy.durationMinutes = call.durationMinutes; 238 copy.ongoing = call.ongoing; 239 copy.bandAtEnd = call.bandAtEnd; 240 return copy; 241 } 242 243 /** Creates a proto for a normal {@code DataCallSession} with default values. */ getDefaultProto(@pnType int apnTypeBitmask)244 private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) { 245 DataCallSession proto = new DataCallSession(); 246 proto.dimension = RANDOM.nextInt(); 247 proto.isMultiSim = SimSlotState.isMultiSim(); 248 proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId()); 249 proto.apnTypeBitmask = apnTypeBitmask; 250 proto.carrierId = mPhone.getCarrierId(); 251 proto.isRoaming = getIsRoaming(); 252 proto.oosAtEnd = false; 253 proto.ratSwitchCount = 0L; 254 proto.isOpportunistic = getIsOpportunistic(); 255 proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; 256 proto.setupFailed = false; 257 proto.failureCause = DataFailCause.NONE; 258 proto.suggestedRetryMillis = 0; 259 proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 260 proto.durationMinutes = 0; 261 proto.ongoing = true; 262 return proto; 263 } 264 getIsRoaming()265 private boolean getIsRoaming() { 266 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 267 ServiceState serviceState = 268 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 269 return serviceState != null ? serviceState.getRoaming() : false; 270 } 271 getIsOpportunistic()272 private boolean getIsOpportunistic() { 273 SubscriptionController subController = SubscriptionController.getInstance(); 274 return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false; 275 } 276 getIsOos()277 private boolean getIsOos() { 278 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 279 ServiceState serviceState = 280 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 281 return serviceState != null 282 ? serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE 283 : false; 284 } 285 logi(String format, Object... args)286 private void logi(String format, Object... args) { 287 Rlog.i(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 288 } 289 loge(String format, Object... args)290 private void loge(String format, Object... args) { 291 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 292 } 293 294 @VisibleForTesting getTimeMillis()295 protected long getTimeMillis() { 296 return SystemClock.elapsedRealtime(); 297 } 298 } 299