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 android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED; 20 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED; 21 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERING; 22 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS; 23 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT; 24 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO; 25 import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE; 26 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN; 27 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE; 28 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE; 29 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NR; 30 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 31 import static android.util.Patterns.EMAIL_ADDRESS; 32 33 import android.annotation.Nullable; 34 import android.os.SystemClock; 35 import android.telephony.AccessNetworkConstants; 36 import android.telephony.AccessNetworkConstants.TransportType; 37 import android.telephony.Annotation.NetworkType; 38 import android.telephony.TelephonyManager; 39 import android.telephony.ims.ImsReasonInfo; 40 import android.telephony.ims.ProvisioningManager; 41 import android.telephony.ims.RegistrationManager.ImsRegistrationState; 42 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities; 43 import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability; 44 import android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.telephony.Phone; 48 import com.android.internal.telephony.PhoneFactory; 49 import com.android.internal.telephony.imsphone.ImsPhone; 50 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats; 51 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination; 52 import com.android.telephony.Rlog; 53 54 import java.util.regex.Pattern; 55 56 /** Tracks IMS registration metrics for each phone. */ 57 public class ImsStats { 58 private static final String TAG = ImsStats.class.getSimpleName(); 59 60 /** 61 * Minimal duration of the registration state. 62 * 63 * <p>Registration state (including changes in capable/available features) with duration shorter 64 * than this will be ignored as they are considered transient states. 65 */ 66 private static final long MIN_REGISTRATION_DURATION_MILLIS = 1L * SECOND_IN_MILLIS; 67 68 /** 69 * Maximum length of the extra message in the termination reason. 70 * 71 * <p>If the extra message is longer than this length, it will be truncated. 72 */ 73 private static final int MAX_EXTRA_MESSAGE_LENGTH = 128; 74 75 /** Pattern used to match UUIDs in IMS extra messages for filtering. */ 76 private static final Pattern PATTERN_UUID = 77 Pattern.compile( 78 "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"); 79 80 /** Replacement for UUIDs. */ 81 private static final String REPLACEMENT_UUID = "<UUID_REDACTED>"; 82 83 /** 84 * Pattern used to match URI (e.g. sip, tel) in IMS extra messages for filtering. 85 * 86 * <p>NOTE: this simple pattern aims to catch the most common URI schemes. It is not meant to be 87 * RFC-complaint. 88 */ 89 private static final Pattern PATTERN_URI = 90 Pattern.compile("([a-zA-Z]{2,}:)" + EMAIL_ADDRESS.pattern()); 91 92 /** Replacement for URI. */ 93 private static final String REPLACEMENT_URI = "$1<REDACTED>"; 94 95 /** 96 * Pattern used to match IPv4 addresses in IMS extra messages for filtering. 97 * 98 * <p>This is a copy of {@code android.util.Patterns.IP_ADDRESS}, which is deprecated and might 99 * be removed in the future. 100 */ 101 private static final Pattern PATTERN_IPV4 = 102 Pattern.compile( 103 "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" 104 + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" 105 + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" 106 + "|[1-9][0-9]|[0-9]))"); 107 108 /** Replacement for IPv4 addresses. */ 109 private static final String REPLACEMENT_IPV4 = "<IPV4_REDACTED>"; 110 111 /** 112 * Pattern used to match IPv6 addresses in IMS extra messages for filtering. 113 * 114 * <p>NOTE: this pattern aims to catch the most common IPv6 addresses. It is not meant to be 115 * RFC-complaint or free of false positives. 116 */ 117 private static final Pattern PATTERN_IPV6 = 118 Pattern.compile( 119 // Full address 120 "([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}" 121 // Abbreviated address, e.g. 2001:4860:4860::8888 122 + "|([0-9a-fA-F]{1,4}:){1,6}(:[0-9a-fA-F]{1,4}){1,6}" 123 // Abbreviated network address, e.g. 2607:F8B0:: 124 + "|([0-9a-fA-F]{1,4}:){1,7}:" 125 // Abbreviated address, e.g. ::1 126 + "|:(:[0-9a-fA-F]{1,4}){1,7}"); 127 128 /** Replacement for IPv6 addresses. */ 129 private static final String REPLACEMENT_IPV6 = "<IPV6_REDACTED>"; 130 131 /** 132 * Pattern used to match potential IMEI values in IMS extra messages for filtering. 133 * 134 * <p>This includes segmented IMEI or IMEI/SV, as well as unsegmented IMEI/SV. 135 */ 136 private static final Pattern PATTERN_IMEI = 137 Pattern.compile( 138 "(^|[^0-9])(?:" 139 // IMEI, AABBBBBB-CCCCCC-D format; IMEI/SV, AABBBBBB-CCCCCC-EE format 140 + "[0-9]{8}-[0-9]{6}-[0-9][0-9]?" 141 // IMEI, AA-BBBBBB-CCCCCC-D format; IMEI/SV, AA-BBBBBB-CCCCCC-EE format 142 + "|[0-9]{2}-[0-9]{6}-[0-9]{6}-[0-9][0-9]?" 143 // IMEI/SV, unsegmented 144 + "|[0-9]{16}" 145 + ")($|[^0-9])"); 146 147 /** Replacement for IMEI. */ 148 private static final String REPLACEMENT_IMEI = "$1<IMEI_REDACTED>$2"; 149 150 /** 151 * Pattern used to match potential unsegmented IMEI/IMSI values in IMS extra messages for 152 * filtering. 153 */ 154 private static final Pattern PATTERN_UNSEGMENTED_IMEI_IMSI = 155 Pattern.compile("(^|[^0-9])[0-9]{15}($|[^0-9])"); 156 157 /** Replacement for unsegmented IMEI/IMSI. */ 158 private static final String REPLACEMENT_UNSEGMENTED_IMEI_IMSI = "$1<IMEI_IMSI_REDACTED>$2"; 159 160 /** 161 * Pattern used to match hostnames in IMS extra messages for filtering. 162 * 163 * <p>This pattern differs from {@link android.util.Patterns.DOMAIN_NAME} in a few ways: it 164 * requires the name to have at least 3 segments (shorter names are nearly always public or 165 * typos, i.e. missing space after period), does not check the validity of TLDs, and does not 166 * support punycodes in TLDs. 167 */ 168 private static final Pattern PATTERN_HOSTNAME = 169 Pattern.compile("([0-9a-zA-Z][0-9a-zA-Z_\\-]{0,61}[0-9a-zA-Z]\\.){2,}[a-zA-Z]{2,}"); 170 171 /** Replacement for hostnames. */ 172 private static final String REPLACEMENT_HOSTNAME = "<HOSTNAME_REDACTED>"; 173 174 /** 175 * Pattern used to match potential IDs in IMS extra messages for filtering. 176 * 177 * <p>This pattern target numbers that are potential IDs in unknown formats. It should be 178 * replaced after all other replacements are done to ensure complete and correct filtering. 179 * 180 * <p>Specifically, this pattern looks for any number (including hex) that is separated by dots 181 * or dashes has at least 6 significant digits, and any unsegmented numbers that has at least 5 182 * significant digits. 183 */ 184 private static final Pattern PATTERN_UNKNOWN_ID = 185 Pattern.compile( 186 "(^|[^0-9a-fA-F])(([-\\.]?0)*[1-9a-fA-F]([-\\.]?[0-9a-fA-F]){5,}" 187 + "|0*[1-9a-fA-F]([0-9a-fA-F]){4,})"); 188 189 /** Replacement for potential IDs. */ 190 private static final String REPLACEMENT_UNKNOWN_ID = "$1<ID_REDACTED>"; 191 192 private final ImsPhone mPhone; 193 private final PersistAtomsStorage mStorage; 194 195 @ImsRegistrationState private int mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED; 196 197 private long mLastTimestamp; 198 @Nullable private ImsRegistrationStats mLastRegistrationStats; 199 200 // Available features are those reported by ImsService to be available for use. 201 private MmTelCapabilities mLastAvailableFeatures = new MmTelCapabilities(); 202 203 // Capable features (enabled by device/carrier). Theses are available before IMS is registered 204 // and not necessarily updated when RAT changes. 205 private final MmTelCapabilities mLastWwanCapableFeatures = new MmTelCapabilities(); 206 private final MmTelCapabilities mLastWlanCapableFeatures = new MmTelCapabilities(); 207 ImsStats(ImsPhone phone)208 public ImsStats(ImsPhone phone) { 209 mPhone = phone; 210 mStorage = PhoneFactory.getMetricsCollector().getAtomsStorage(); 211 } 212 213 /** 214 * Finalizes the durations of the current IMS registration stats segment. 215 * 216 * <p>This method is also invoked whenever the registration state, feature capability, or 217 * feature availability changes. 218 */ conclude()219 public synchronized void conclude() { 220 long now = getTimeMillis(); 221 222 // Currently not tracking time spent on registering. 223 if (mLastRegistrationState == REGISTRATION_STATE_REGISTERED) { 224 ImsRegistrationStats stats = copyOf(mLastRegistrationStats); 225 long duration = now - mLastTimestamp; 226 227 if (duration < MIN_REGISTRATION_DURATION_MILLIS) { 228 logw("conclude: discarding transient stats, duration=%d", duration); 229 } else { 230 stats.registeredMillis = duration; 231 232 stats.voiceAvailableMillis = 233 mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0; 234 stats.videoAvailableMillis = 235 mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0; 236 stats.utAvailableMillis = 237 mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0; 238 stats.smsAvailableMillis = 239 mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0; 240 241 MmTelCapabilities lastCapableFeatures = 242 stats.rat == TelephonyManager.NETWORK_TYPE_IWLAN 243 ? mLastWlanCapableFeatures 244 : mLastWwanCapableFeatures; 245 stats.voiceCapableMillis = 246 lastCapableFeatures.isCapable(CAPABILITY_TYPE_VOICE) ? duration : 0; 247 stats.videoCapableMillis = 248 lastCapableFeatures.isCapable(CAPABILITY_TYPE_VIDEO) ? duration : 0; 249 stats.utCapableMillis = 250 lastCapableFeatures.isCapable(CAPABILITY_TYPE_UT) ? duration : 0; 251 stats.smsCapableMillis = 252 lastCapableFeatures.isCapable(CAPABILITY_TYPE_SMS) ? duration : 0; 253 254 mStorage.addImsRegistrationStats(stats); 255 } 256 } 257 258 mLastTimestamp = now; 259 } 260 261 /** Updates the stats when registered features changed. */ onImsCapabilitiesChanged( @msRegistrationTech int radioTech, MmTelCapabilities capabilities)262 public synchronized void onImsCapabilitiesChanged( 263 @ImsRegistrationTech int radioTech, MmTelCapabilities capabilities) { 264 conclude(); 265 266 boolean ratChanged = false; 267 @NetworkType int newRat = convertRegistrationTechToNetworkType(radioTech); 268 if (mLastRegistrationStats != null && mLastRegistrationStats.rat != newRat) { 269 mLastRegistrationStats.rat = newRat; 270 ratChanged = true; 271 } 272 273 boolean voiceAvailableNow = capabilities.isCapable(CAPABILITY_TYPE_VOICE); 274 boolean voiceAvailabilityChanged = 275 (mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE) != voiceAvailableNow); 276 mLastAvailableFeatures = capabilities; 277 278 // Notify voice RAT change if 1. RAT changed while voice over IMS is available, or 2. voice 279 // over IMS availability changed 280 if ((ratChanged && voiceAvailableNow) || voiceAvailabilityChanged) { 281 mPhone.getDefaultPhone().getServiceStateTracker().getServiceStateStats() 282 .onImsVoiceRegistrationChanged(); 283 } 284 } 285 286 /** Updates the stats when capable features changed. */ onSetFeatureResponse( @mTelCapability int feature, @ImsRegistrationTech int network, int value)287 public synchronized void onSetFeatureResponse( 288 @MmTelCapability int feature, @ImsRegistrationTech int network, int value) { 289 MmTelCapabilities lastCapableFeatures = getLastCapableFeaturesForTech(network); 290 if (lastCapableFeatures != null) { 291 conclude(); 292 if (value == ProvisioningManager.PROVISIONING_VALUE_ENABLED) { 293 lastCapableFeatures.addCapabilities(feature); 294 } else { 295 lastCapableFeatures.removeCapabilities(feature); 296 } 297 } 298 } 299 300 /** Updates the stats when IMS registration is progressing. */ onImsRegistering(@ransportType int imsRadioTech)301 public synchronized void onImsRegistering(@TransportType int imsRadioTech) { 302 conclude(); 303 304 mLastRegistrationStats = getDefaultImsRegistrationStats(); 305 mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech); 306 mLastRegistrationState = REGISTRATION_STATE_REGISTERING; 307 } 308 309 /** Updates the stats when IMS registration succeeds. */ onImsRegistered(@ransportType int imsRadioTech)310 public synchronized void onImsRegistered(@TransportType int imsRadioTech) { 311 conclude(); 312 313 // NOTE: mLastRegistrationStats can be null (no registering phase). 314 if (mLastRegistrationStats == null) { 315 mLastRegistrationStats = getDefaultImsRegistrationStats(); 316 } 317 mLastRegistrationStats.rat = convertTransportTypeToNetworkType(imsRadioTech); 318 mLastRegistrationState = REGISTRATION_STATE_REGISTERED; 319 } 320 321 /** Updates the stats and generates a termination atom when IMS registration fails/ends. */ onImsUnregistered(ImsReasonInfo reasonInfo)322 public synchronized void onImsUnregistered(ImsReasonInfo reasonInfo) { 323 conclude(); 324 325 // Generate end reason atom. 326 // NOTE: mLastRegistrationStats can be null (no registering phase). 327 ImsRegistrationTermination termination = new ImsRegistrationTermination(); 328 if (mLastRegistrationStats != null) { 329 termination.carrierId = mLastRegistrationStats.carrierId; 330 termination.ratAtEnd = getRatAtEnd(mLastRegistrationStats.rat); 331 } else { 332 termination.carrierId = mPhone.getDefaultPhone().getCarrierId(); 333 // We cannot tell whether the registration was intended for WWAN or WLAN 334 termination.ratAtEnd = TelephonyManager.NETWORK_TYPE_UNKNOWN; 335 } 336 termination.isMultiSim = SimSlotState.isMultiSim(); 337 termination.setupFailed = (mLastRegistrationState != REGISTRATION_STATE_REGISTERED); 338 termination.reasonCode = reasonInfo.getCode(); 339 termination.extraCode = reasonInfo.getExtraCode(); 340 termination.extraMessage = filterExtraMessage(reasonInfo.getExtraMessage()); 341 termination.count = 1; 342 mStorage.addImsRegistrationTermination(termination); 343 344 // Reset state to unregistered. 345 mLastRegistrationState = REGISTRATION_STATE_NOT_REGISTERED; 346 mLastRegistrationStats = null; 347 mLastAvailableFeatures = new MmTelCapabilities(); 348 } 349 350 /** 351 * Returns the current RAT used for IMS voice registration, or {@link 352 * TelephonyManager#NETWORK_TYPE_UNKNOWN} if there isn't any. 353 */ 354 @NetworkType 355 @VisibleForTesting getImsVoiceRadioTech()356 public synchronized int getImsVoiceRadioTech() { 357 if (mLastRegistrationStats == null 358 || !mLastAvailableFeatures.isCapable(CAPABILITY_TYPE_VOICE)) { 359 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 360 } 361 return mLastRegistrationStats.rat; 362 } 363 364 @NetworkType getRatAtEnd(@etworkType int lastStateRat)365 private int getRatAtEnd(@NetworkType int lastStateRat) { 366 return lastStateRat == TelephonyManager.NETWORK_TYPE_IWLAN ? lastStateRat : getWwanPsRat(); 367 } 368 369 @NetworkType convertTransportTypeToNetworkType(@ransportType int transportType)370 private int convertTransportTypeToNetworkType(@TransportType int transportType) { 371 switch (transportType) { 372 case AccessNetworkConstants.TRANSPORT_TYPE_WWAN: 373 return getWwanPsRat(); 374 case AccessNetworkConstants.TRANSPORT_TYPE_WLAN: 375 return TelephonyManager.NETWORK_TYPE_IWLAN; 376 default: 377 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 378 } 379 } 380 381 @NetworkType getWwanPsRat()382 private int getWwanPsRat() { 383 return ServiceStateStats.getDataRat(mPhone.getServiceStateTracker().getServiceState()); 384 } 385 getDefaultImsRegistrationStats()386 private ImsRegistrationStats getDefaultImsRegistrationStats() { 387 Phone phone = mPhone.getDefaultPhone(); 388 ImsRegistrationStats stats = new ImsRegistrationStats(); 389 stats.carrierId = phone.getCarrierId(); 390 stats.simSlotIndex = phone.getPhoneId(); 391 return stats; 392 } 393 394 @Nullable getLastCapableFeaturesForTech(@msRegistrationTech int radioTech)395 private MmTelCapabilities getLastCapableFeaturesForTech(@ImsRegistrationTech int radioTech) { 396 switch (radioTech) { 397 case REGISTRATION_TECH_NONE: 398 return null; 399 case REGISTRATION_TECH_IWLAN: 400 return mLastWlanCapableFeatures; 401 default: 402 return mLastWwanCapableFeatures; 403 } 404 } 405 406 @NetworkType convertRegistrationTechToNetworkType(@msRegistrationTech int radioTech)407 private int convertRegistrationTechToNetworkType(@ImsRegistrationTech int radioTech) { 408 switch (radioTech) { 409 case REGISTRATION_TECH_NONE: 410 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 411 case REGISTRATION_TECH_LTE: 412 return TelephonyManager.NETWORK_TYPE_LTE; 413 case REGISTRATION_TECH_IWLAN: 414 return TelephonyManager.NETWORK_TYPE_IWLAN; 415 case REGISTRATION_TECH_NR: 416 return TelephonyManager.NETWORK_TYPE_NR; 417 default: 418 loge("convertRegistrationTechToNetworkType: unknown radio tech %d", radioTech); 419 return getWwanPsRat(); 420 } 421 } 422 copyOf(ImsRegistrationStats source)423 private static ImsRegistrationStats copyOf(ImsRegistrationStats source) { 424 ImsRegistrationStats dest = new ImsRegistrationStats(); 425 426 dest.carrierId = source.carrierId; 427 dest.simSlotIndex = source.simSlotIndex; 428 dest.rat = source.rat; 429 dest.registeredMillis = source.registeredMillis; 430 dest.voiceCapableMillis = source.voiceCapableMillis; 431 dest.voiceAvailableMillis = source.voiceAvailableMillis; 432 dest.smsCapableMillis = source.smsCapableMillis; 433 dest.smsAvailableMillis = source.smsAvailableMillis; 434 dest.videoCapableMillis = source.videoCapableMillis; 435 dest.videoAvailableMillis = source.videoAvailableMillis; 436 dest.utCapableMillis = source.utCapableMillis; 437 dest.utAvailableMillis = source.utAvailableMillis; 438 439 return dest; 440 } 441 442 @VisibleForTesting getTimeMillis()443 protected long getTimeMillis() { 444 return SystemClock.elapsedRealtime(); 445 } 446 447 /** Filters IMS extra messages to ensure length limit and remove IDs. */ filterExtraMessage(@ullable String str)448 public static String filterExtraMessage(@Nullable String str) { 449 if (str == null) { 450 return ""; 451 } 452 453 str = PATTERN_UUID.matcher(str).replaceAll(REPLACEMENT_UUID); 454 str = PATTERN_URI.matcher(str).replaceAll(REPLACEMENT_URI); 455 str = PATTERN_HOSTNAME.matcher(str).replaceAll(REPLACEMENT_HOSTNAME); 456 str = PATTERN_IPV4.matcher(str).replaceAll(REPLACEMENT_IPV4); 457 str = PATTERN_IPV6.matcher(str).replaceAll(REPLACEMENT_IPV6); 458 str = PATTERN_IMEI.matcher(str).replaceAll(REPLACEMENT_IMEI); 459 str = PATTERN_UNSEGMENTED_IMEI_IMSI.matcher(str) 460 .replaceAll(REPLACEMENT_UNSEGMENTED_IMEI_IMSI); 461 str = PATTERN_UNKNOWN_ID.matcher(str).replaceAll(REPLACEMENT_UNKNOWN_ID); 462 463 return str.length() > MAX_EXTRA_MESSAGE_LENGTH 464 ? str.substring(0, MAX_EXTRA_MESSAGE_LENGTH) 465 : str; 466 } 467 logw(String format, Object... args)468 private void logw(String format, Object... args) { 469 Rlog.w(TAG, "[" + mPhone.getPhoneId() + "] " + String.format(format, args)); 470 } 471 loge(String format, Object... args)472 private void loge(String format, Object... args) { 473 Rlog.e(TAG, "[" + mPhone.getPhoneId() + "] " + String.format(format, args)); 474 } 475 } 476