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