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.TelephonyManager; 33 import android.telephony.data.ApnSetting.ProtocolType; 34 import android.telephony.data.DataCallResponse; 35 import android.telephony.data.DataService; 36 import android.telephony.data.DataService.DeactivateDataReason; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.telephony.Phone; 40 import com.android.internal.telephony.PhoneFactory; 41 import com.android.internal.telephony.ServiceStateTracker; 42 import com.android.internal.telephony.SubscriptionController; 43 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; 44 import com.android.telephony.Rlog; 45 46 import java.util.Arrays; 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 62 public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15; 63 DataCallSessionStats(Phone phone)64 public DataCallSessionStats(Phone phone) { 65 mPhone = phone; 66 } 67 68 /** Creates a new ongoing atom when data call is set up. */ onSetupDataCall(@pnType int apnTypeBitMask)69 public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) { 70 mDataCallSession = getDefaultProto(apnTypeBitMask); 71 mStartTime = getTimeMillis(); 72 PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this); 73 } 74 75 /** 76 * Updates the ongoing dataCall's atom for data call response event. 77 * 78 * @param response setup Data call response 79 * @param currentRat The data call current Network Type 80 * @param apnTypeBitmask APN type bitmask 81 * @param protocol Data connection protocol 82 * @param failureCause failure cause as per android.telephony.DataFailCause 83 */ onSetupDataCallResponse( @ullable DataCallResponse response, @NetworkType int currentRat, @ApnType int apnTypeBitmask, @ProtocolType int protocol, @DataFailureCause int failureCause)84 public synchronized void onSetupDataCallResponse( 85 @Nullable DataCallResponse response, 86 @NetworkType int currentRat, 87 @ApnType int apnTypeBitmask, 88 @ProtocolType int protocol, 89 @DataFailureCause int failureCause) { 90 // there should've been a call to onSetupDataCall to initiate the atom, 91 // so this method is being called out of order -> no metric will be logged 92 if (mDataCallSession == null) { 93 loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); 94 return; 95 } 96 97 if (currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 98 mDataCallSession.ratAtEnd = currentRat; 99 mDataCallSession.bandAtEnd = 100 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 101 ? 0 102 : ServiceStateStats.getBand(mPhone); 103 } 104 105 // only set if apn hasn't been set during setup 106 if (mDataCallSession.apnTypeBitmask == 0) { 107 mDataCallSession.apnTypeBitmask = apnTypeBitmask; 108 } 109 110 mDataCallSession.ipType = protocol; 111 mDataCallSession.failureCause = failureCause; 112 if (response != null) { 113 mDataCallSession.suggestedRetryMillis = 114 (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE); 115 // If setup has failed, then store the atom 116 if (failureCause != DataFailCause.NONE) { 117 mDataCallSession.setupFailed = true; 118 endDataCallSession(); 119 } 120 } 121 } 122 123 /** 124 * Updates the dataCall atom when data call is deactivated. 125 * 126 * @param reason Deactivate reason 127 */ setDeactivateDataCallReason(@eactivateDataReason int reason)128 public synchronized void setDeactivateDataCallReason(@DeactivateDataReason int reason) { 129 // there should've been another call to initiate the atom, 130 // so this method is being called out of order -> no metric will be logged 131 if (mDataCallSession == null) { 132 loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated."); 133 return; 134 } 135 switch (reason) { 136 case DataService.REQUEST_REASON_NORMAL: 137 mDataCallSession.deactivateReason = 138 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_NORMAL; 139 break; 140 case DataService.REQUEST_REASON_SHUTDOWN: 141 mDataCallSession.deactivateReason = 142 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_RADIO_OFF; 143 break; 144 case DataService.REQUEST_REASON_HANDOVER: 145 mDataCallSession.deactivateReason = 146 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_HANDOVER; 147 break; 148 default: 149 mDataCallSession.deactivateReason = 150 DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 151 break; 152 } 153 } 154 155 /** 156 * Stores the atom when DataConnection reaches DISCONNECTED state. 157 * 158 * @param failureCause failure cause as per android.telephony.DataFailCause 159 */ onDataCallDisconnected(@ataFailureCause int failureCause)160 public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) { 161 // there should've been another call to initiate the atom, 162 // so this method is being called out of order -> no atom will be saved 163 // this also happens when DataConnection is created, which is expected 164 if (mDataCallSession == null) { 165 logi("onDataCallDisconnected: no DataCallSession atom has been initiated."); 166 return; 167 } 168 mDataCallSession.failureCause = failureCause; 169 mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime); 170 endDataCallSession(); 171 } 172 173 /** 174 * Updates the atom when a handover fails. Note we only record distinct failure causes, as in 175 * most cases retry failures are due to the same cause. 176 * 177 * @param failureCause failure cause as per android.telephony.DataFailCause 178 */ onHandoverFailure(@ataFailureCause int failureCause, @NetworkType int sourceRat, @NetworkType int targetRat)179 public synchronized void onHandoverFailure(@DataFailureCause int failureCause, 180 @NetworkType int sourceRat, @NetworkType int targetRat) { 181 if (mDataCallSession != null 182 && mDataCallSession.handoverFailureCauses.length 183 < SIZE_LIMIT_HANDOVER_FAILURES) { 184 185 int[] failureCauses = mDataCallSession.handoverFailureCauses; 186 int[] handoverFailureRats = mDataCallSession.handoverFailureRat; 187 int failureDirection = sourceRat | (targetRat << 16); 188 189 for (int i = 0; i < failureCauses.length; i++) { 190 if (failureCauses[i] == failureCause 191 && handoverFailureRats[i] == failureDirection) { 192 return; 193 } 194 } 195 196 mDataCallSession.handoverFailureCauses = Arrays.copyOf( 197 failureCauses, failureCauses.length + 1); 198 mDataCallSession.handoverFailureCauses[failureCauses.length] = failureCause; 199 200 mDataCallSession.handoverFailureRat = Arrays.copyOf(handoverFailureRats, 201 handoverFailureRats.length + 1); 202 mDataCallSession.handoverFailureRat[handoverFailureRats.length] = failureDirection; 203 } 204 } 205 206 /** 207 * Updates the atom when data registration state or RAT changes. 208 * 209 * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data 210 * registration state change. 211 */ onDrsOrRatChanged(@etworkType int currentRat)212 public synchronized void onDrsOrRatChanged(@NetworkType int currentRat) { 213 if (mDataCallSession != null && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 214 if (mDataCallSession.ratAtEnd != currentRat) { 215 mDataCallSession.ratSwitchCount++; 216 mDataCallSession.ratAtEnd = currentRat; 217 } 218 // band may have changed even if RAT was the same 219 mDataCallSession.bandAtEnd = 220 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 221 ? 0 222 : ServiceStateStats.getBand(mPhone); 223 } 224 } 225 226 /** Stores the current unmetered network types information in permanent storage. */ onUnmeteredUpdate(@etworkType int networkType)227 public void onUnmeteredUpdate(@NetworkType int networkType) { 228 mAtomsStorage 229 .addUnmeteredNetworks( 230 mPhone.getPhoneId(), 231 mPhone.getCarrierId(), 232 TelephonyManager.getBitMaskForNetworkType(networkType)); 233 } 234 235 /** 236 * Take a snapshot of the on-going data call segment to add to the atom storage. 237 * 238 * Note the following fields are reset after the snapshot: 239 * - rat switch count 240 * - handover failure causes 241 * - handover failure rats 242 */ conclude()243 public synchronized void conclude() { 244 if (mDataCallSession != null) { 245 DataCallSession call = copyOf(mDataCallSession); 246 long nowMillis = getTimeMillis(); 247 call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime); 248 mStartTime = nowMillis; 249 mDataCallSession.ratSwitchCount = 0L; 250 mDataCallSession.handoverFailureCauses = new int[0]; 251 mDataCallSession.handoverFailureRat = new int[0]; 252 mAtomsStorage.addDataCallSession(call); 253 } 254 } 255 256 /** Put the current data call to an end after being uploaded to AtomStorage. */ endDataCallSession()257 private void endDataCallSession() { 258 mDataCallSession.oosAtEnd = getIsOos(); 259 mDataCallSession.ongoing = false; 260 // store for the data call list event, after DataCall is disconnected and entered into 261 // inactive mode 262 PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this); 263 mAtomsStorage.addDataCallSession(mDataCallSession); 264 mDataCallSession = null; 265 } 266 convertMillisToMinutes(long millis)267 private static long convertMillisToMinutes(long millis) { 268 return Math.round(millis / 60000.0); 269 } 270 copyOf(DataCallSession call)271 private static DataCallSession copyOf(DataCallSession call) { 272 DataCallSession copy = new DataCallSession(); 273 copy.dimension = call.dimension; 274 copy.isMultiSim = call.isMultiSim; 275 copy.isEsim = call.isEsim; 276 copy.apnTypeBitmask = call.apnTypeBitmask; 277 copy.carrierId = call.carrierId; 278 copy.isRoaming = call.isRoaming; 279 copy.ratAtEnd = call.ratAtEnd; 280 copy.oosAtEnd = call.oosAtEnd; 281 copy.ratSwitchCount = call.ratSwitchCount; 282 copy.isOpportunistic = call.isOpportunistic; 283 copy.ipType = call.ipType; 284 copy.setupFailed = call.setupFailed; 285 copy.failureCause = call.failureCause; 286 copy.suggestedRetryMillis = call.suggestedRetryMillis; 287 copy.deactivateReason = call.deactivateReason; 288 copy.durationMinutes = call.durationMinutes; 289 copy.ongoing = call.ongoing; 290 copy.bandAtEnd = call.bandAtEnd; 291 copy.handoverFailureCauses = Arrays.copyOf(call.handoverFailureCauses, 292 call.handoverFailureCauses.length); 293 copy.handoverFailureRat = Arrays.copyOf(call.handoverFailureRat, 294 call.handoverFailureRat.length); 295 return copy; 296 } 297 298 /** Creates a proto for a normal {@code DataCallSession} with default values. */ getDefaultProto(@pnType int apnTypeBitmask)299 private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) { 300 DataCallSession proto = new DataCallSession(); 301 proto.dimension = RANDOM.nextInt(); 302 proto.isMultiSim = SimSlotState.isMultiSim(); 303 proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId()); 304 proto.apnTypeBitmask = apnTypeBitmask; 305 proto.carrierId = mPhone.getCarrierId(); 306 proto.isRoaming = getIsRoaming(); 307 proto.oosAtEnd = false; 308 proto.ratSwitchCount = 0L; 309 proto.isOpportunistic = getIsOpportunistic(); 310 proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; 311 proto.setupFailed = false; 312 proto.failureCause = DataFailCause.NONE; 313 proto.suggestedRetryMillis = 0; 314 proto.deactivateReason = DATA_CALL_SESSION__DEACTIVATE_REASON__DEACTIVATE_REASON_UNKNOWN; 315 proto.durationMinutes = 0; 316 proto.ongoing = true; 317 proto.handoverFailureCauses = new int[0]; 318 proto.handoverFailureRat = new int[0]; 319 return proto; 320 } 321 getIsRoaming()322 private boolean getIsRoaming() { 323 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 324 ServiceState serviceState = 325 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 326 return serviceState != null ? serviceState.getRoaming() : false; 327 } 328 getIsOpportunistic()329 private boolean getIsOpportunistic() { 330 SubscriptionController subController = SubscriptionController.getInstance(); 331 return subController != null ? subController.isOpportunistic(mPhone.getSubId()) : false; 332 } 333 getIsOos()334 private boolean getIsOos() { 335 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 336 ServiceState serviceState = 337 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 338 return serviceState != null 339 ? serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE 340 : false; 341 } 342 logi(String format, Object... args)343 private void logi(String format, Object... args) { 344 Rlog.i(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 345 } 346 loge(String format, Object... args)347 private void loge(String format, Object... args) { 348 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 349 } 350 351 @VisibleForTesting getTimeMillis()352 protected long getTimeMillis() { 353 return SystemClock.elapsedRealtime(); 354 } 355 } 356