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