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.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; 20 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; 21 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; 22 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO; 23 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT; 24 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND; 25 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; 26 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND; 27 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; 28 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND; 29 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST; 30 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW; 31 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST; 32 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL; 33 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW; 34 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST; 35 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW; 36 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 37 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST; 38 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW; 39 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT; 40 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN; 41 42 import android.annotation.Nullable; 43 import android.content.Context; 44 import android.net.wifi.WifiInfo; 45 import android.net.wifi.WifiManager; 46 import android.os.SystemClock; 47 import android.telecom.VideoProfile; 48 import android.telecom.VideoProfile.VideoState; 49 import android.telephony.Annotation.NetworkType; 50 import android.telephony.DisconnectCause; 51 import android.telephony.ServiceState; 52 import android.telephony.TelephonyManager; 53 import android.telephony.ims.ImsReasonInfo; 54 import android.telephony.ims.ImsStreamMediaProfile; 55 import android.util.LongSparseArray; 56 import android.util.SparseArray; 57 import android.util.SparseIntArray; 58 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.telephony.Call; 61 import com.android.internal.telephony.Connection; 62 import com.android.internal.telephony.DriverCall; 63 import com.android.internal.telephony.GsmCdmaConnection; 64 import com.android.internal.telephony.Phone; 65 import com.android.internal.telephony.PhoneConstants; 66 import com.android.internal.telephony.PhoneFactory; 67 import com.android.internal.telephony.ServiceStateTracker; 68 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 69 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; 70 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec; 71 import com.android.internal.telephony.uicc.UiccController; 72 import com.android.telephony.Rlog; 73 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.HashSet; 77 import java.util.List; 78 import java.util.Set; 79 import java.util.stream.Collectors; 80 81 /** Collects voice call events per phone ID for the pulled atom. */ 82 public class VoiceCallSessionStats { 83 private static final String TAG = VoiceCallSessionStats.class.getSimpleName(); 84 85 /** Upper bounds of each call setup duration category in milliseconds. */ 86 private static final int CALL_SETUP_DURATION_UNKNOWN = 0; 87 private static final int CALL_SETUP_DURATION_EXTREMELY_FAST = 400; 88 private static final int CALL_SETUP_DURATION_ULTRA_FAST = 700; 89 private static final int CALL_SETUP_DURATION_VERY_FAST = 1000; 90 private static final int CALL_SETUP_DURATION_FAST = 1500; 91 private static final int CALL_SETUP_DURATION_NORMAL = 2500; 92 private static final int CALL_SETUP_DURATION_SLOW = 4000; 93 private static final int CALL_SETUP_DURATION_VERY_SLOW = 6000; 94 private static final int CALL_SETUP_DURATION_ULTRA_SLOW = 10000; 95 // CALL_SETUP_DURATION_EXTREMELY_SLOW has no upper bound (it includes everything above 10000) 96 97 /** Number of buckets for codec quality, from UNKNOWN to FULLBAND. */ 98 private static final int CODEC_QUALITY_COUNT = 5; 99 100 /** 101 * Threshold to calculate the main audio codec quality of the call. 102 * 103 * The audio codec quality was equal to or greater than the main audio codec quality for 104 * at least 70% of the call. 105 */ 106 private static final int MAIN_CODEC_QUALITY_THRESHOLD = 70; 107 108 /** Holds the audio codec value for CS calls. */ 109 private static final SparseIntArray CS_CODEC_MAP = buildGsmCdmaCodecMap(); 110 111 /** Holds the audio codec value for IMS calls. */ 112 private static final SparseIntArray IMS_CODEC_MAP = buildImsCodecMap(); 113 114 /** Holds setup duration buckets with values as their upper bounds in milliseconds. */ 115 private static final SparseIntArray CALL_SETUP_DURATION_MAP = buildCallSetupDurationMap(); 116 117 /** 118 * Tracks statistics for each call connection, indexed with ID returned by {@link 119 * #getConnectionId}. 120 */ 121 private final SparseArray<VoiceCallSession> mCallProtos = new SparseArray<>(); 122 123 /** 124 * Tracks usage of codecs for each call. The outer array is used to map each connection id to 125 * the corresponding codec usage. The inner array is used to map timestamp (key) with the 126 * codec in use (value). 127 */ 128 private final SparseArray<LongSparseArray<Integer>> mCodecUsage = new SparseArray<>(); 129 130 /** 131 * Tracks call RAT usage. 132 * 133 * <p>RAT usage is mainly tied to phones rather than calls, since each phone can have multiple 134 * concurrent calls, and we do not want to count the RAT duration multiple times. 135 */ 136 private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker(); 137 138 private final int mPhoneId; 139 private final Phone mPhone; 140 141 private final PersistAtomsStorage mAtomsStorage = 142 PhoneFactory.getMetricsCollector().getAtomsStorage(); 143 private final UiccController mUiccController = UiccController.getInstance(); 144 VoiceCallSessionStats(int phoneId, Phone phone)145 public VoiceCallSessionStats(int phoneId, Phone phone) { 146 mPhoneId = phoneId; 147 mPhone = phone; 148 } 149 150 /* CS calls */ 151 152 /** Updates internal states when previous CS calls are accepted to track MT call setup time. */ onRilAcceptCall(List<Connection> connections)153 public synchronized void onRilAcceptCall(List<Connection> connections) { 154 for (Connection conn : connections) { 155 addCall(conn); 156 } 157 } 158 159 /** Updates internal states when a CS MO call is created. */ onRilDial(Connection conn)160 public synchronized void onRilDial(Connection conn) { 161 addCall(conn); 162 } 163 164 /** 165 * Updates internal states when CS calls are created or terminated, or CS call state is changed. 166 */ onRilCallListChanged(List<GsmCdmaConnection> connections)167 public synchronized void onRilCallListChanged(List<GsmCdmaConnection> connections) { 168 for (Connection conn : connections) { 169 int id = getConnectionId(conn); 170 if (!mCallProtos.contains(id)) { 171 // handle new connections 172 if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) { 173 addCall(conn); 174 checkCallSetup(conn, mCallProtos.get(id)); 175 } else { 176 logd("onRilCallListChanged: skip adding disconnected connection"); 177 } 178 } else { 179 VoiceCallSession proto = mCallProtos.get(id); 180 // handle call state change 181 checkCallSetup(conn, proto); 182 // handle terminated connections 183 if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) { 184 proto.bearerAtEnd = getBearer(conn); // should be CS 185 proto.disconnectReasonCode = conn.getDisconnectCause(); 186 proto.disconnectExtraCode = conn.getPreciseDisconnectCause(); 187 proto.disconnectExtraMessage = conn.getVendorDisconnectCause(); 188 finishCall(id); 189 } 190 } 191 } 192 // NOTE: we cannot check stray connections (CS call in our list but not in RIL), as 193 // GsmCdmaCallTracker can call this with a partial list 194 } 195 196 /* IMS calls */ 197 198 /** Updates internal states when an IMS MO call is created. */ onImsDial(ImsPhoneConnection conn)199 public synchronized void onImsDial(ImsPhoneConnection conn) { 200 addCall(conn); 201 if (conn.hasRttTextStream()) { 202 setRttStarted(conn); 203 } 204 } 205 206 /** Updates internal states when an IMS MT call is created. */ onImsCallReceived(ImsPhoneConnection conn)207 public synchronized void onImsCallReceived(ImsPhoneConnection conn) { 208 addCall(conn); 209 if (conn.hasRttTextStream()) { 210 setRttStarted(conn); 211 } 212 } 213 214 /** Updates internal states when previous IMS calls are accepted to track MT call setup time. */ onImsAcceptCall(List<Connection> connections)215 public synchronized void onImsAcceptCall(List<Connection> connections) { 216 for (Connection conn : connections) { 217 addCall(conn); 218 } 219 } 220 221 /** Updates internal states when an IMS call is terminated. */ onImsCallTerminated( @ullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo)222 public synchronized void onImsCallTerminated( 223 @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) { 224 if (conn == null) { 225 List<Integer> imsConnIds = getImsConnectionIds(); 226 if (imsConnIds.size() == 1) { 227 loge("onImsCallTerminated: ending IMS call w/ conn=null"); 228 finishImsCall(imsConnIds.get(0), reasonInfo); 229 } else { 230 loge("onImsCallTerminated: %d IMS calls w/ conn=null", imsConnIds.size()); 231 } 232 } else { 233 int id = getConnectionId(conn); 234 if (mCallProtos.contains(id)) { 235 finishImsCall(id, reasonInfo); 236 } else { 237 loge("onImsCallTerminated: untracked connection"); 238 // fake a call so at least some info can be tracked 239 addCall(conn); 240 finishImsCall(id, reasonInfo); 241 } 242 } 243 } 244 245 /** Updates internal states when RTT is started on an IMS call. */ onRttStarted(ImsPhoneConnection conn)246 public synchronized void onRttStarted(ImsPhoneConnection conn) { 247 setRttStarted(conn); 248 } 249 250 /* general & misc. */ 251 252 /** Updates internal states when audio codec for a call is changed. */ onAudioCodecChanged(Connection conn, int audioQuality)253 public synchronized void onAudioCodecChanged(Connection conn, int audioQuality) { 254 int id = getConnectionId(conn); 255 VoiceCallSession proto = mCallProtos.get(id); 256 if (proto == null) { 257 loge("onAudioCodecChanged: untracked connection"); 258 return; 259 } 260 int codec = audioQualityToCodec(proto.bearerAtEnd, audioQuality); 261 proto.codecBitmask |= (1L << codec); 262 263 if (mCodecUsage.contains(id)) { 264 mCodecUsage.get(id).append(getTimeMillis(), codec); 265 } else { 266 LongSparseArray<Integer> arr = new LongSparseArray<>(); 267 arr.append(getTimeMillis(), codec); 268 mCodecUsage.put(id, arr); 269 } 270 } 271 272 /** Updates internal states when video state changes. */ onVideoStateChange( ImsPhoneConnection conn, @VideoState int videoState)273 public synchronized void onVideoStateChange( 274 ImsPhoneConnection conn, @VideoState int videoState) { 275 int id = getConnectionId(conn); 276 VoiceCallSession proto = mCallProtos.get(id); 277 if (proto == null) { 278 loge("onVideoStateChange: untracked connection"); 279 return; 280 } 281 logd("Video state = " + videoState); 282 if (videoState != VideoProfile.STATE_AUDIO_ONLY) { 283 proto.videoEnabled = true; 284 } 285 } 286 287 /** Updates internal states when multiparty state changes. */ onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty)288 public synchronized void onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty) { 289 int id = getConnectionId(conn); 290 VoiceCallSession proto = mCallProtos.get(id); 291 if (proto == null) { 292 loge("onMultipartyChange: untracked connection"); 293 return; 294 } 295 logd("Multiparty = " + isMultiParty); 296 if (isMultiParty) { 297 proto.isMultiparty = true; 298 } 299 } 300 301 /** 302 * Updates internal states when a call changes state to track setup time and status. 303 * 304 * <p>This is currently mainly used by IMS since CS call states are updated through {@link 305 * #onRilCallListChanged}. 306 */ onCallStateChanged(Call call)307 public synchronized void onCallStateChanged(Call call) { 308 for (Connection conn : call.getConnections()) { 309 VoiceCallSession proto = mCallProtos.get(getConnectionId(conn)); 310 if (proto != null) { 311 checkCallSetup(conn, proto); 312 } else { 313 loge("onCallStateChanged: untracked connection"); 314 } 315 } 316 } 317 318 /** Updates internal states when an IMS call is handover to a CS call. */ onRilSrvccStateChanged(int state)319 public synchronized void onRilSrvccStateChanged(int state) { 320 List<Connection> handoverConnections = null; 321 if (mPhone.getImsPhone() != null) { 322 loge("onRilSrvccStateChanged: ImsPhone is null"); 323 } else { 324 handoverConnections = mPhone.getImsPhone().getHandoverConnection(); 325 } 326 List<Integer> imsConnIds; 327 if (handoverConnections == null) { 328 imsConnIds = getImsConnectionIds(); 329 loge("onRilSrvccStateChanged: ImsPhone has no handover, we have %d", imsConnIds.size()); 330 } else { 331 imsConnIds = 332 handoverConnections.stream() 333 .map(VoiceCallSessionStats::getConnectionId) 334 .collect(Collectors.toList()); 335 } 336 switch (state) { 337 case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED: 338 // connection will now be CS 339 for (int id : imsConnIds) { 340 VoiceCallSession proto = mCallProtos.get(id); 341 proto.srvccCompleted = true; 342 proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; 343 } 344 break; 345 case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED: 346 for (int id : imsConnIds) { 347 mCallProtos.get(id).srvccFailureCount++; 348 } 349 break; 350 case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED: 351 for (int id : imsConnIds) { 352 mCallProtos.get(id).srvccCancellationCount++; 353 } 354 break; 355 default: // including STARTED and NONE, do nothing 356 } 357 } 358 359 /** Updates internal states when RAT changes. */ onServiceStateChanged(ServiceState state)360 public synchronized void onServiceStateChanged(ServiceState state) { 361 if (hasCalls()) { 362 updateRatTracker(state); 363 } 364 } 365 366 /* internal */ 367 368 /** 369 * Adds a call connection. 370 * 371 * <p>Should be called when the call is created, and when setup begins (upon {@code 372 * RilRequest.RIL_REQUEST_ANSWER} or {@code ImsCommand.IMS_CMD_ACCEPT}). 373 */ addCall(Connection conn)374 private void addCall(Connection conn) { 375 int id = getConnectionId(conn); 376 if (mCallProtos.contains(id)) { 377 // mostly handles ringing MT call getting accepted (MT call setup begins) 378 logd("addCall: resetting setup info"); 379 VoiceCallSession proto = mCallProtos.get(id); 380 proto.setupBeginMillis = getTimeMillis(); 381 proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 382 } else { 383 int bearer = getBearer(conn); 384 ServiceState serviceState = getServiceState(); 385 @NetworkType int rat = getRat(serviceState); 386 387 VoiceCallSession proto = new VoiceCallSession(); 388 389 proto.bearerAtStart = bearer; 390 proto.bearerAtEnd = bearer; 391 proto.direction = getDirection(conn); 392 proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 393 proto.setupFailed = true; 394 proto.disconnectReasonCode = conn.getDisconnectCause(); 395 proto.disconnectExtraCode = conn.getPreciseDisconnectCause(); 396 proto.disconnectExtraMessage = conn.getVendorDisconnectCause(); 397 proto.ratAtStart = rat; 398 proto.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN; 399 proto.ratAtEnd = rat; 400 proto.ratSwitchCount = 0L; 401 proto.codecBitmask = 0L; 402 proto.simSlotIndex = mPhoneId; 403 proto.isMultiSim = SimSlotState.isMultiSim(); 404 proto.isEsim = SimSlotState.isEsim(mPhoneId); 405 proto.carrierId = mPhone.getCarrierId(); 406 proto.srvccCompleted = false; 407 proto.srvccFailureCount = 0L; 408 proto.srvccCancellationCount = 0L; 409 proto.rttEnabled = false; 410 proto.isEmergency = conn.isEmergencyCall(); 411 proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false; 412 proto.isMultiparty = conn.isMultiparty(); 413 414 // internal fields for tracking 415 proto.setupBeginMillis = getTimeMillis(); 416 417 proto.concurrentCallCountAtStart = mCallProtos.size(); 418 mCallProtos.put(id, proto); 419 420 // RAT call count needs to be updated 421 updateRatTracker(serviceState); 422 } 423 } 424 425 /** Sends the call metrics to persist storage when it is finished. */ finishCall(int connectionId)426 private void finishCall(int connectionId) { 427 VoiceCallSession proto = mCallProtos.get(connectionId); 428 if (proto == null) { 429 loge("finishCall: could not find call to be removed"); 430 return; 431 } 432 mCallProtos.delete(connectionId); 433 proto.concurrentCallCountAtEnd = mCallProtos.size(); 434 435 // Calculate signal strength at the end of the call 436 proto.signalStrengthAtEnd = getSignalStrength(proto.ratAtEnd); 437 438 // Calculate main codec quality 439 proto.mainCodecQuality = finalizeMainCodecQuality(connectionId); 440 441 // ensure internal fields are cleared 442 proto.setupBeginMillis = 0L; 443 444 // sanitize for javanano & StatsEvent 445 if (proto.disconnectExtraMessage == null) { 446 proto.disconnectExtraMessage = ""; 447 } 448 449 // Retry populating carrier ID if it was invalid 450 if (proto.carrierId <= 0) { 451 proto.carrierId = mPhone.getCarrierId(); 452 } 453 454 mAtomsStorage.addVoiceCallSession(proto); 455 456 // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls) 457 if (!hasCalls()) { 458 mRatUsage.conclude(getTimeMillis()); 459 mAtomsStorage.addVoiceCallRatUsage(mRatUsage); 460 mRatUsage.clear(); 461 } 462 } 463 setRttStarted(ImsPhoneConnection conn)464 private void setRttStarted(ImsPhoneConnection conn) { 465 VoiceCallSession proto = mCallProtos.get(getConnectionId(conn)); 466 if (proto == null) { 467 loge("onRttStarted: untracked connection"); 468 return; 469 } 470 // should be IMS w/o SRVCC 471 if (proto.bearerAtStart != getBearer(conn) || proto.bearerAtEnd != getBearer(conn)) { 472 loge("onRttStarted: connection bearer mismatch but proceeding"); 473 } 474 proto.rttEnabled = true; 475 } 476 477 /** Returns a {@link Set} of Connection IDs so RAT usage can be correctly tracked. */ getConnectionIds()478 private Set<Integer> getConnectionIds() { 479 Set<Integer> ids = new HashSet<>(); 480 for (int i = 0; i < mCallProtos.size(); i++) { 481 ids.add(mCallProtos.keyAt(i)); 482 } 483 return ids; 484 } 485 getImsConnectionIds()486 private List<Integer> getImsConnectionIds() { 487 List<Integer> imsConnIds = new ArrayList<>(mCallProtos.size()); 488 for (int i = 0; i < mCallProtos.size(); i++) { 489 if (mCallProtos.valueAt(i).bearerAtEnd 490 == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) { 491 imsConnIds.add(mCallProtos.keyAt(i)); 492 } 493 } 494 return imsConnIds; 495 } 496 hasCalls()497 private boolean hasCalls() { 498 return mCallProtos.size() > 0; 499 } 500 checkCallSetup(Connection conn, VoiceCallSession proto)501 private void checkCallSetup(Connection conn, VoiceCallSession proto) { 502 if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) { 503 proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis); 504 proto.setupDuration = classifySetupDuration(proto.setupDurationMillis); 505 proto.setupBeginMillis = 0L; 506 } 507 // Clear setupFailed if call now active, but otherwise leave it unchanged 508 // This block is executed only once, when call becomes active for the first time. 509 if (proto.setupFailed && conn.getState() == Call.State.ACTIVE) { 510 proto.setupFailed = false; 511 // Track RAT when voice call is connected. 512 ServiceState serviceState = getServiceState(); 513 proto.ratAtConnected = getRat(serviceState); 514 // Reset list of codecs with the last codec at the present time. In this way, we 515 // track codec quality only after call is connected and not while ringing. 516 resetCodecList(conn); 517 } 518 } 519 updateRatTracker(ServiceState state)520 private void updateRatTracker(ServiceState state) { 521 @NetworkType int rat = getRat(state); 522 int band = ServiceStateStats.getBand(state, rat); 523 524 mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds()); 525 for (int i = 0; i < mCallProtos.size(); i++) { 526 VoiceCallSession proto = mCallProtos.valueAt(i); 527 if (proto.ratAtEnd != rat) { 528 proto.ratSwitchCount++; 529 proto.ratAtEnd = rat; 530 } 531 proto.bandAtEnd = band; 532 // assuming that SIM carrier ID does not change during the call 533 } 534 } 535 finishImsCall(int id, ImsReasonInfo reasonInfo)536 private void finishImsCall(int id, ImsReasonInfo reasonInfo) { 537 VoiceCallSession proto = mCallProtos.get(id); 538 proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; 539 proto.disconnectReasonCode = reasonInfo.mCode; 540 proto.disconnectExtraCode = reasonInfo.mExtraCode; 541 proto.disconnectExtraMessage = ImsStats.filterExtraMessage(reasonInfo.mExtraMessage); 542 finishCall(id); 543 } 544 getServiceState()545 private @Nullable ServiceState getServiceState() { 546 ServiceStateTracker tracker = mPhone.getServiceStateTracker(); 547 return tracker != null ? tracker.getServiceState() : null; 548 } 549 getDirection(Connection conn)550 private static int getDirection(Connection conn) { 551 return conn.isIncoming() 552 ? VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT 553 : VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO; 554 } 555 getBearer(Connection conn)556 private static int getBearer(Connection conn) { 557 int phoneType = conn.getPhoneType(); 558 switch (phoneType) { 559 case PhoneConstants.PHONE_TYPE_GSM: 560 case PhoneConstants.PHONE_TYPE_CDMA: 561 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; 562 case PhoneConstants.PHONE_TYPE_IMS: 563 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; 564 default: 565 loge("getBearer: unknown phoneType=%d", phoneType); 566 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; 567 } 568 } 569 getRat(@ullable ServiceState state)570 private @NetworkType int getRat(@Nullable ServiceState state) { 571 if (state == null) { 572 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 573 } 574 boolean isWifiCall = 575 mPhone.getImsPhone() != null 576 && mPhone.getImsPhone().isWifiCallingEnabled() 577 && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN; 578 return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType(); 579 } 580 581 /** Returns the signal strength. */ getSignalStrength(@etworkType int rat)582 private int getSignalStrength(@NetworkType int rat) { 583 if (rat == TelephonyManager.NETWORK_TYPE_IWLAN) { 584 return getSignalStrengthWifi(); 585 } else { 586 return getSignalStrengthCellular(); 587 } 588 } 589 590 /** Returns the signal strength of WiFi. */ getSignalStrengthWifi()591 private int getSignalStrengthWifi() { 592 WifiManager wifiManager = 593 (WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE); 594 WifiInfo wifiInfo = wifiManager.getConnectionInfo(); 595 int result = VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN; 596 if (wifiInfo != null) { 597 int level = wifiManager.calculateSignalLevel(wifiInfo.getRssi()); 598 int max = wifiManager.getMaxSignalLevel(); 599 // Scale result into 0 to 4 range. 600 result = VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT 601 * level / max; 602 logd("WiFi level: " + result + " (" + level + "/" + max + ")"); 603 } 604 return result; 605 } 606 607 /** Returns the signal strength of cellular RAT. */ getSignalStrengthCellular()608 private int getSignalStrengthCellular() { 609 return mPhone.getSignalStrength().getLevel(); 610 } 611 612 /** Resets the list of codecs used for the connection with only the codec currently in use. */ resetCodecList(Connection conn)613 private void resetCodecList(Connection conn) { 614 int id = getConnectionId(conn); 615 LongSparseArray<Integer> codecUsage = mCodecUsage.get(id); 616 if (codecUsage != null) { 617 int lastCodec = codecUsage.valueAt(codecUsage.size() - 1); 618 LongSparseArray<Integer> arr = new LongSparseArray<>(); 619 arr.append(getTimeMillis(), lastCodec); 620 mCodecUsage.put(id, arr); 621 } 622 } 623 624 /** Returns the main codec quality used during the call. */ finalizeMainCodecQuality(int connectionId)625 private int finalizeMainCodecQuality(int connectionId) { 626 // Retrieve information about codec usage for this call and remove it from main array. 627 if (!mCodecUsage.contains(connectionId)) { 628 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; 629 } 630 LongSparseArray<Integer> codecUsage = mCodecUsage.get(connectionId); 631 mCodecUsage.delete(connectionId); 632 633 // Append fake entry at the end, to facilitate the calculation of time for each codec. 634 codecUsage.put(getTimeMillis(), AudioCodec.AUDIO_CODEC_UNKNOWN); 635 636 // Calculate array with time for each quality 637 int totalTime = 0; 638 long[] timePerQuality = new long[CODEC_QUALITY_COUNT]; 639 for (int i = 0; i < codecUsage.size() - 1; i++) { 640 long time = codecUsage.keyAt(i + 1) - codecUsage.keyAt(i); 641 int quality = getCodecQuality(codecUsage.valueAt(i)); 642 timePerQuality[quality] += time; 643 totalTime += time; 644 } 645 logd("Time per codec quality = " + Arrays.toString(timePerQuality)); 646 647 // We calculate 70% duration of the call as the threshold for the main audio codec quality 648 // and iterate on all codec qualities. As soon as the sum of codec duration is greater than 649 // the threshold, we have identified the main codec quality. 650 long timeAtMinimumQuality = 0; 651 long timeThreshold = totalTime * MAIN_CODEC_QUALITY_THRESHOLD / 100; 652 for (int i = CODEC_QUALITY_COUNT - 1; i >= 0; i--) { 653 timeAtMinimumQuality += timePerQuality[i]; 654 if (timeAtMinimumQuality >= timeThreshold) { 655 return i; 656 } 657 } 658 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; 659 } 660 getCodecQuality(int codec)661 private int getCodecQuality(int codec) { 662 switch(codec) { 663 case AudioCodec.AUDIO_CODEC_AMR: 664 case AudioCodec.AUDIO_CODEC_QCELP13K: 665 case AudioCodec.AUDIO_CODEC_EVRC: 666 case AudioCodec.AUDIO_CODEC_EVRC_B: 667 case AudioCodec.AUDIO_CODEC_EVRC_NW: 668 case AudioCodec.AUDIO_CODEC_GSM_EFR: 669 case AudioCodec.AUDIO_CODEC_GSM_FR: 670 case AudioCodec.AUDIO_CODEC_GSM_HR: 671 case AudioCodec.AUDIO_CODEC_G711U: 672 case AudioCodec.AUDIO_CODEC_G723: 673 case AudioCodec.AUDIO_CODEC_G711A: 674 case AudioCodec.AUDIO_CODEC_G722: 675 case AudioCodec.AUDIO_CODEC_G711AB: 676 case AudioCodec.AUDIO_CODEC_G729: 677 case AudioCodec.AUDIO_CODEC_EVS_NB: 678 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND; 679 case AudioCodec.AUDIO_CODEC_AMR_WB: 680 case AudioCodec.AUDIO_CODEC_EVS_WB: 681 case AudioCodec.AUDIO_CODEC_EVRC_WB: 682 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND; 683 case AudioCodec.AUDIO_CODEC_EVS_SWB: 684 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND; 685 case AudioCodec.AUDIO_CODEC_EVS_FB: 686 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND; 687 default: 688 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN; 689 } 690 } 691 isSetupFinished(@ullable Call call)692 private static boolean isSetupFinished(@Nullable Call call) { 693 // NOTE: when setup is finished for MO calls, it is not successful yet. 694 if (call != null) { 695 switch (call.getState()) { 696 case ACTIVE: // MT setup: accepted to ACTIVE 697 case ALERTING: // MO setup: dial to ALERTING 698 return true; 699 default: // do nothing 700 } 701 } 702 return false; 703 } 704 audioQualityToCodec(int bearer, int audioQuality)705 private static int audioQualityToCodec(int bearer, int audioQuality) { 706 switch (bearer) { 707 case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS: 708 return CS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN); 709 case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS: 710 return IMS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN); 711 default: 712 loge("audioQualityToCodec: unknown bearer %d", bearer); 713 return AudioCodec.AUDIO_CODEC_UNKNOWN; 714 } 715 } 716 classifySetupDuration(int durationMillis)717 private static int classifySetupDuration(int durationMillis) { 718 // keys in CALL_SETUP_DURATION_MAP are upper bounds in ascending order 719 for (int i = 0; i < CALL_SETUP_DURATION_MAP.size(); i++) { 720 if (durationMillis < CALL_SETUP_DURATION_MAP.keyAt(i)) { 721 return CALL_SETUP_DURATION_MAP.valueAt(i); 722 } 723 } 724 return VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW; 725 } 726 727 /** 728 * Generates an ID for each connection, which should be the same for IMS and CS connections 729 * involved in the same SRVCC. 730 * 731 * <p>Among the fields copied from ImsPhoneConnection to GsmCdmaConnection during SRVCC, the 732 * Connection's create time seems to be the best choice for ID (assuming no multiple calls in a 733 * millisecond). The 64-bit time is truncated to 32-bit so it can be used as an index in various 734 * data structures, which is good for calls shorter than 49 days. 735 */ getConnectionId(Connection conn)736 private static int getConnectionId(Connection conn) { 737 return conn == null ? 0 : (int) conn.getCreateTime(); 738 } 739 740 @VisibleForTesting getTimeMillis()741 protected long getTimeMillis() { 742 return SystemClock.elapsedRealtime(); 743 } 744 logd(String format, Object... args)745 private static void logd(String format, Object... args) { 746 Rlog.d(TAG, String.format(format, args)); 747 } 748 loge(String format, Object... args)749 private static void loge(String format, Object... args) { 750 Rlog.e(TAG, String.format(format, args)); 751 } 752 buildGsmCdmaCodecMap()753 private static SparseIntArray buildGsmCdmaCodecMap() { 754 SparseIntArray map = new SparseIntArray(); 755 map.put(DriverCall.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR); 756 map.put(DriverCall.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB); 757 map.put(DriverCall.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR); 758 map.put(DriverCall.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR); 759 map.put(DriverCall.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR); 760 map.put(DriverCall.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC); 761 map.put(DriverCall.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B); 762 map.put(DriverCall.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB); 763 map.put(DriverCall.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW); 764 return map; 765 } 766 buildImsCodecMap()767 private static SparseIntArray buildImsCodecMap() { 768 SparseIntArray map = new SparseIntArray(); 769 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR); 770 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB); 771 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K, AudioCodec.AUDIO_CODEC_QCELP13K); 772 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC); 773 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B); 774 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB); 775 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW); 776 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR); 777 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR); 778 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR); 779 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711U, AudioCodec.AUDIO_CODEC_G711U); 780 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G723, AudioCodec.AUDIO_CODEC_G723); 781 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711A, AudioCodec.AUDIO_CODEC_G711A); 782 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G722, AudioCodec.AUDIO_CODEC_G722); 783 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711AB, AudioCodec.AUDIO_CODEC_G711AB); 784 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G729, AudioCodec.AUDIO_CODEC_G729); 785 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB, AudioCodec.AUDIO_CODEC_EVS_NB); 786 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB, AudioCodec.AUDIO_CODEC_EVS_WB); 787 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB, AudioCodec.AUDIO_CODEC_EVS_SWB); 788 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB, AudioCodec.AUDIO_CODEC_EVS_FB); 789 return map; 790 } 791 buildCallSetupDurationMap()792 private static SparseIntArray buildCallSetupDurationMap() { 793 SparseIntArray map = new SparseIntArray(); 794 795 map.put( 796 CALL_SETUP_DURATION_UNKNOWN, 797 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN); 798 map.put( 799 CALL_SETUP_DURATION_EXTREMELY_FAST, 800 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST); 801 map.put( 802 CALL_SETUP_DURATION_ULTRA_FAST, 803 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST); 804 map.put( 805 CALL_SETUP_DURATION_VERY_FAST, 806 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST); 807 map.put( 808 CALL_SETUP_DURATION_FAST, 809 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST); 810 map.put( 811 CALL_SETUP_DURATION_NORMAL, 812 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL); 813 map.put( 814 CALL_SETUP_DURATION_SLOW, 815 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW); 816 map.put( 817 CALL_SETUP_DURATION_VERY_SLOW, 818 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW); 819 map.put( 820 CALL_SETUP_DURATION_ULTRA_SLOW, 821 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW); 822 // anything above would be CALL_SETUP_DURATION_EXTREMELY_SLOW 823 824 return map; 825 } 826 } 827