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__IP_TYPE__APN_PROTOCOL_IPV4; 20 21 import android.annotation.Nullable; 22 import android.net.NetworkCapabilities; 23 import android.os.SystemClock; 24 import android.telephony.Annotation.ApnType; 25 import android.telephony.Annotation.DataFailureCause; 26 import android.telephony.Annotation.NetworkType; 27 import android.telephony.DataFailCause; 28 import android.telephony.NetworkRegistrationInfo; 29 import android.telephony.ServiceState; 30 import android.telephony.SubscriptionInfo; 31 import android.telephony.SubscriptionManager; 32 import android.telephony.TelephonyManager; 33 import android.telephony.data.ApnSetting; 34 import android.telephony.data.ApnSetting.ProtocolType; 35 import android.telephony.data.DataCallResponse; 36 import android.telephony.data.DataService; 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.data.DataNetwork; 43 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession; 44 import com.android.internal.telephony.satellite.SatelliteController; 45 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 46 import com.android.internal.telephony.subscription.SubscriptionManagerService; 47 import com.android.telephony.Rlog; 48 49 import java.util.Arrays; 50 import java.util.Random; 51 52 /** Collects data call change events per DataConnection for the pulled atom. */ 53 public class DataCallSessionStats { 54 private static final String TAG = DataCallSessionStats.class.getSimpleName(); 55 56 private final Phone mPhone; 57 private long mStartTime; 58 @Nullable private DataCallSession mDataCallSession; 59 60 private final PersistAtomsStorage mAtomsStorage = 61 PhoneFactory.getMetricsCollector().getAtomsStorage(); 62 63 private static final Random RANDOM = new Random(); 64 65 public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15; 66 67 private final DefaultNetworkMonitor mDefaultNetworkMonitor; 68 private final SatelliteController mSatelliteController; 69 DataCallSessionStats(Phone phone)70 public DataCallSessionStats(Phone phone) { 71 mPhone = phone; 72 mDefaultNetworkMonitor = PhoneFactory.getMetricsCollector().getDefaultNetworkMonitor(); 73 mSatelliteController = SatelliteController.getInstance(); 74 } 75 isSystemDefaultNetworkMobile()76 private boolean isSystemDefaultNetworkMobile() { 77 NetworkCapabilities nc = mDefaultNetworkMonitor.getNetworkCapabilities(); 78 return nc != null && nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); 79 } 80 81 /** Creates a new ongoing atom when data call is set up. */ onSetupDataCall(@pnType int apnTypeBitMask, boolean isSatellite)82 public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask, 83 boolean isSatellite) { 84 mDataCallSession = getDefaultProto(apnTypeBitMask, isSatellite); 85 mStartTime = getTimeMillis(); 86 PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this); 87 } 88 89 /** 90 * Updates the ongoing dataCall's atom for data call response event. 91 * 92 * @param response setup Data call response 93 * @param currentRat The data call current Network Type 94 * @param apnTypeBitmask APN type bitmask 95 * @param protocol Data connection protocol 96 * @param failureCause The raw failure cause from modem/IWLAN data service. 97 */ onSetupDataCallResponse( @ullable DataCallResponse response, @NetworkType int currentRat, @ApnType int apnTypeBitmask, @ProtocolType int protocol, int failureCause)98 public synchronized void onSetupDataCallResponse( 99 @Nullable DataCallResponse response, 100 @NetworkType int currentRat, 101 @ApnType int apnTypeBitmask, 102 @ProtocolType int protocol, 103 int failureCause) { 104 // there should've been a call to onSetupDataCall to initiate the atom, 105 // so this method is being called out of order -> no metric will be logged 106 if (mDataCallSession == null) { 107 loge("onSetupDataCallResponse: no DataCallSession atom has been initiated."); 108 return; 109 } 110 111 if (currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 112 mDataCallSession.ratAtEnd = currentRat; 113 mDataCallSession.bandAtEnd = 114 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 115 ? 0 116 : ServiceStateStats.getBand(mPhone); 117 // Limitation: Will not capture IKE mobility between Backup Calling <-> WiFi Calling. 118 mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN 119 && isSystemDefaultNetworkMobile(); 120 } 121 122 // only set if apn hasn't been set during setup 123 if (mDataCallSession.apnTypeBitmask == 0) { 124 mDataCallSession.apnTypeBitmask = apnTypeBitmask; 125 } 126 127 mDataCallSession.ipType = protocol; 128 mDataCallSession.failureCause = failureCause; 129 if (response != null) { 130 mDataCallSession.suggestedRetryMillis = 131 (int) Math.min(response.getRetryDurationMillis(), Integer.MAX_VALUE); 132 // If setup has failed, then store the atom 133 if (failureCause != DataFailCause.NONE) { 134 mDataCallSession.setupFailed = true; 135 endDataCallSession(); 136 } 137 } 138 } 139 140 /** 141 * Updates the dataCall atom when data call is deactivated. 142 * 143 * @param reason Tear down reason 144 */ setDeactivateDataCallReason(@ataNetwork.TearDownReason int reason)145 public synchronized void setDeactivateDataCallReason(@DataNetwork.TearDownReason int reason) { 146 // there should've been another call to initiate the atom, 147 // so this method is being called out of order -> no metric will be logged 148 if (mDataCallSession == null) { 149 loge("setDeactivateDataCallReason: no DataCallSession atom has been initiated."); 150 return; 151 } 152 // Skip the pre-U enum. See enum DataDeactivateReasonEnum in enums.proto 153 mDataCallSession.deactivateReason = reason + DataService.REQUEST_REASON_HANDOVER + 1; 154 } 155 156 /** 157 * Stores the atom when DataConnection reaches DISCONNECTED state. 158 * 159 * @param failureCause failure cause as per android.telephony.DataFailCause 160 */ onDataCallDisconnected(@ataFailureCause int failureCause)161 public synchronized void onDataCallDisconnected(@DataFailureCause int failureCause) { 162 // there should've been another call to initiate the atom, 163 // so this method is being called out of order -> no atom will be saved 164 // this also happens when DataConnection is created, which is expected 165 if (mDataCallSession == null) { 166 logi("onDataCallDisconnected: no DataCallSession atom has been initiated."); 167 return; 168 } 169 mDataCallSession.failureCause = failureCause; 170 mDataCallSession.durationMinutes = convertMillisToMinutes(getTimeMillis() - mStartTime); 171 endDataCallSession(); 172 } 173 174 /** 175 * Updates the atom when a handover fails. Note we only record distinct failure causes, as in 176 * most cases retry failures are due to the same cause. 177 * 178 * @param failureCause failure cause as per android.telephony.DataFailCause 179 */ onHandoverFailure(@ataFailureCause int failureCause, @NetworkType int sourceRat, @NetworkType int targetRat)180 public synchronized void onHandoverFailure(@DataFailureCause int failureCause, 181 @NetworkType int sourceRat, @NetworkType int targetRat) { 182 if (mDataCallSession != null 183 && mDataCallSession.handoverFailureCauses.length 184 < SIZE_LIMIT_HANDOVER_FAILURES) { 185 186 int[] failureCauses = mDataCallSession.handoverFailureCauses; 187 int[] handoverFailureRats = mDataCallSession.handoverFailureRat; 188 int failureDirection = sourceRat | (targetRat << 16); 189 190 for (int i = 0; i < failureCauses.length; i++) { 191 if (failureCauses[i] == failureCause 192 && handoverFailureRats[i] == failureDirection) { 193 return; 194 } 195 } 196 197 mDataCallSession.handoverFailureCauses = Arrays.copyOf( 198 failureCauses, failureCauses.length + 1); 199 mDataCallSession.handoverFailureCauses[failureCauses.length] = failureCause; 200 201 mDataCallSession.handoverFailureRat = Arrays.copyOf(handoverFailureRats, 202 handoverFailureRats.length + 1); 203 mDataCallSession.handoverFailureRat[handoverFailureRats.length] = failureDirection; 204 } 205 } 206 207 /** 208 * Updates the atom when data registration state or RAT changes. 209 * 210 * <p>NOTE: in {@link ServiceStateTracker}, change of channel number will trigger data 211 * registration state change. 212 */ onDrsOrRatChanged(@etworkType int currentRat)213 public synchronized void onDrsOrRatChanged(@NetworkType int currentRat) { 214 if (mDataCallSession != null && currentRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) { 215 if (mDataCallSession.ratAtEnd != currentRat) { 216 mDataCallSession.ratSwitchCount++; 217 mDataCallSession.ratAtEnd = currentRat; 218 mDataCallSession.isIwlanCrossSim = currentRat == TelephonyManager.NETWORK_TYPE_IWLAN 219 && isSystemDefaultNetworkMobile(); 220 } 221 // band may have changed even if RAT was the same 222 mDataCallSession.bandAtEnd = 223 (currentRat == TelephonyManager.NETWORK_TYPE_IWLAN) 224 ? 0 225 : ServiceStateStats.getBand(mPhone); 226 } 227 } 228 229 /** Stores the current unmetered network types information in permanent storage. */ onUnmeteredUpdate(@etworkType int networkType)230 public void onUnmeteredUpdate(@NetworkType int networkType) { 231 mAtomsStorage 232 .addUnmeteredNetworks( 233 mPhone.getPhoneId(), 234 mPhone.getCarrierId(), 235 TelephonyManager.getBitMaskForNetworkType(networkType)); 236 } 237 238 /** 239 * Take a snapshot of the on-going data call segment to add to the atom storage. 240 * 241 * Note the following fields are reset after the snapshot: 242 * - rat switch count 243 * - handover failure causes 244 * - handover failure rats 245 */ conclude()246 public synchronized void conclude() { 247 if (mDataCallSession != null) { 248 DataCallSession call = copyOf(mDataCallSession); 249 long nowMillis = getTimeMillis(); 250 call.durationMinutes = convertMillisToMinutes(nowMillis - mStartTime); 251 mStartTime = nowMillis; 252 mDataCallSession.ratSwitchCount = 0L; 253 mDataCallSession.handoverFailureCauses = new int[0]; 254 mDataCallSession.handoverFailureRat = new int[0]; 255 mAtomsStorage.addDataCallSession(call); 256 } 257 } 258 259 /** Put the current data call to an end after being uploaded to AtomStorage. */ endDataCallSession()260 private void endDataCallSession() { 261 mDataCallSession.oosAtEnd = getIsOos(); 262 mDataCallSession.ongoing = false; 263 // set if this data call is established for internet on the non-Dds 264 SubscriptionInfo subInfo = SubscriptionManagerService.getInstance() 265 .getSubscriptionInfo(mPhone.getSubId()); 266 if (mPhone.getSubId() != SubscriptionManager.getDefaultDataSubscriptionId() 267 && ((mDataCallSession.apnTypeBitmask & ApnSetting.TYPE_DEFAULT) 268 == ApnSetting.TYPE_DEFAULT) 269 && subInfo != null && !subInfo.isOpportunistic()) { 270 mDataCallSession.isNonDds = true; 271 } 272 273 // store for the data call list event, after DataCall is disconnected and entered into 274 // inactive mode 275 PhoneFactory.getMetricsCollector().unregisterOngoingDataCallStat(this); 276 mAtomsStorage.addDataCallSession(mDataCallSession); 277 mDataCallSession = null; 278 } 279 convertMillisToMinutes(long millis)280 private static long convertMillisToMinutes(long millis) { 281 return Math.round(millis / 60000.0); 282 } 283 copyOf(DataCallSession call)284 private static DataCallSession copyOf(DataCallSession call) { 285 DataCallSession copy = new DataCallSession(); 286 copy.dimension = call.dimension; 287 copy.isMultiSim = call.isMultiSim; 288 copy.isEsim = call.isEsim; 289 copy.apnTypeBitmask = call.apnTypeBitmask; 290 copy.carrierId = call.carrierId; 291 copy.isRoaming = call.isRoaming; 292 copy.ratAtEnd = call.ratAtEnd; 293 copy.oosAtEnd = call.oosAtEnd; 294 copy.ratSwitchCount = call.ratSwitchCount; 295 copy.isOpportunistic = call.isOpportunistic; 296 copy.ipType = call.ipType; 297 copy.setupFailed = call.setupFailed; 298 copy.failureCause = call.failureCause; 299 copy.suggestedRetryMillis = call.suggestedRetryMillis; 300 copy.deactivateReason = call.deactivateReason; 301 copy.durationMinutes = call.durationMinutes; 302 copy.ongoing = call.ongoing; 303 copy.bandAtEnd = call.bandAtEnd; 304 copy.handoverFailureCauses = Arrays.copyOf(call.handoverFailureCauses, 305 call.handoverFailureCauses.length); 306 copy.handoverFailureRat = Arrays.copyOf(call.handoverFailureRat, 307 call.handoverFailureRat.length); 308 copy.isNonDds = call.isNonDds; 309 copy.isIwlanCrossSim = call.isIwlanCrossSim; 310 copy.isNtn = call.isNtn; 311 copy.isSatelliteTransport = call.isSatelliteTransport; 312 copy.isProvisioningProfile = call.isProvisioningProfile; 313 return copy; 314 } 315 316 /** Creates a proto for a normal {@code DataCallSession} with default values. */ getDefaultProto(@pnType int apnTypeBitmask, boolean isSatellite)317 private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask, 318 boolean isSatellite) { 319 DataCallSession proto = new DataCallSession(); 320 proto.dimension = RANDOM.nextInt(); 321 proto.isMultiSim = SimSlotState.isMultiSim(); 322 proto.isEsim = SimSlotState.isEsim(mPhone.getPhoneId()); 323 proto.apnTypeBitmask = apnTypeBitmask; 324 proto.carrierId = mPhone.getCarrierId(); 325 proto.isRoaming = getIsRoaming(); 326 proto.oosAtEnd = false; 327 proto.ratSwitchCount = 0L; 328 proto.isOpportunistic = getIsOpportunistic(); 329 proto.ipType = DATA_CALL_SESSION__IP_TYPE__APN_PROTOCOL_IPV4; 330 proto.setupFailed = false; 331 proto.failureCause = DataFailCause.NONE; 332 proto.suggestedRetryMillis = 0; 333 proto.deactivateReason = DataNetwork.TEAR_DOWN_REASON_NONE; 334 proto.durationMinutes = 0; 335 proto.ongoing = true; 336 proto.handoverFailureCauses = new int[0]; 337 proto.handoverFailureRat = new int[0]; 338 proto.isNonDds = false; 339 proto.isIwlanCrossSim = false; 340 proto.isNtn = mSatelliteController != null 341 ? mSatelliteController.isInSatelliteModeForCarrierRoaming(mPhone) : false; 342 proto.isSatelliteTransport = isSatellite; 343 proto.isProvisioningProfile = getIsProvisioningProfile(); 344 return proto; 345 } 346 getIsRoaming()347 private boolean getIsRoaming() { 348 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 349 ServiceState serviceState = 350 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 351 return ServiceStateStats.isNetworkRoaming(serviceState, NetworkRegistrationInfo.DOMAIN_PS); 352 } 353 getIsOpportunistic()354 private boolean getIsOpportunistic() { 355 SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() 356 .getSubscriptionInfoInternal(mPhone.getSubId()); 357 return subInfo != null && subInfo.isOpportunistic(); 358 } 359 getIsProvisioningProfile()360 private boolean getIsProvisioningProfile() { 361 SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance() 362 .getSubscriptionInfoInternal(mPhone.getSubId()); 363 try { 364 return subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING; 365 } catch (Exception ex) { 366 loge("getIsProvisioningProfile: " + ex.getMessage()); 367 return false; 368 } 369 } 370 getIsOos()371 private boolean getIsOos() { 372 ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker(); 373 ServiceState serviceState = 374 serviceStateTracker != null ? serviceStateTracker.getServiceState() : null; 375 return serviceState != null 376 && serviceState.getDataRegistrationState() == ServiceState.STATE_OUT_OF_SERVICE; 377 } 378 logi(String format, Object... args)379 private void logi(String format, Object... args) { 380 Rlog.i(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 381 } 382 loge(String format, Object... args)383 private void loge(String format, Object... args) { 384 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args)); 385 } 386 387 @VisibleForTesting getTimeMillis()388 protected long getTimeMillis() { 389 return SystemClock.elapsedRealtime(); 390 } 391 } 392