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