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__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST; 25 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW; 26 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST; 27 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL; 28 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW; 29 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST; 30 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW; 31 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 32 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST; 33 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW; 34 35 import android.annotation.Nullable; 36 import android.os.SystemClock; 37 import android.telephony.Annotation.NetworkType; 38 import android.telephony.DisconnectCause; 39 import android.telephony.ServiceState; 40 import android.telephony.TelephonyManager; 41 import android.telephony.ims.ImsReasonInfo; 42 import android.telephony.ims.ImsStreamMediaProfile; 43 import android.util.SparseArray; 44 import android.util.SparseIntArray; 45 import android.util.SparseLongArray; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.internal.telephony.Call; 49 import com.android.internal.telephony.Connection; 50 import com.android.internal.telephony.DriverCall; 51 import com.android.internal.telephony.GsmCdmaConnection; 52 import com.android.internal.telephony.Phone; 53 import com.android.internal.telephony.PhoneConstants; 54 import com.android.internal.telephony.PhoneFactory; 55 import com.android.internal.telephony.ServiceStateTracker; 56 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 57 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession; 58 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec; 59 import com.android.internal.telephony.uicc.UiccController; 60 import com.android.internal.telephony.uicc.UiccSlot; 61 import com.android.telephony.Rlog; 62 63 import java.util.ArrayList; 64 import java.util.HashSet; 65 import java.util.List; 66 import java.util.Set; 67 import java.util.stream.Collectors; 68 69 /** Collects voice call events per phone ID for the pulled atom. */ 70 public class VoiceCallSessionStats { 71 private static final String TAG = VoiceCallSessionStats.class.getSimpleName(); 72 73 /** Bitmask value of unknown audio codecs. */ 74 private static final long AUDIO_CODEC_UNKNOWN = 1L << AudioCodec.AUDIO_CODEC_UNKNOWN; 75 76 /** Holds the audio codec bitmask value for CS calls. */ 77 private static final SparseLongArray CS_CODEC_MAP = buildGsmCdmaCodecMap(); 78 79 /** Holds the audio codec bitmask value for IMS calls. */ 80 private static final SparseLongArray IMS_CODEC_MAP = buildImsCodecMap(); 81 82 /** Holds setup duration buckets with keys as their lower bounds in milliseconds. */ 83 private static final SparseIntArray CALL_SETUP_DURATION_MAP = buildCallSetupDurationMap(); 84 85 /** 86 * Tracks statistics for each call connection, indexed with ID returned by {@link 87 * #getConnectionId}. 88 */ 89 private final SparseArray<VoiceCallSession> mCallProtos = new SparseArray<>(); 90 91 /** 92 * Tracks call RAT usage. 93 * 94 * <p>RAT usage is mainly tied to phones rather than calls, since each phone can have multiple 95 * concurrent calls, and we do not want to count the RAT duration multiple times. 96 */ 97 private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker(); 98 99 private final int mPhoneId; 100 private final Phone mPhone; 101 102 private final PersistAtomsStorage mAtomsStorage = 103 PhoneFactory.getMetricsCollector().getAtomsStorage(); 104 private final UiccController mUiccController = UiccController.getInstance(); 105 VoiceCallSessionStats(int phoneId, Phone phone)106 public VoiceCallSessionStats(int phoneId, Phone phone) { 107 mPhoneId = phoneId; 108 mPhone = phone; 109 } 110 111 /* CS calls */ 112 113 /** Updates internal states when previous CS calls are accepted to track MT call setup time. */ onRilAcceptCall(List<Connection> connections)114 public synchronized void onRilAcceptCall(List<Connection> connections) { 115 for (Connection conn : connections) { 116 addCall(conn); 117 } 118 } 119 120 /** Updates internal states when a CS MO call is created. */ onRilDial(Connection conn)121 public synchronized void onRilDial(Connection conn) { 122 addCall(conn); 123 } 124 125 /** 126 * Updates internal states when CS calls are created or terminated, or CS call state is changed. 127 */ onRilCallListChanged(List<GsmCdmaConnection> connections)128 public synchronized void onRilCallListChanged(List<GsmCdmaConnection> connections) { 129 for (Connection conn : connections) { 130 int id = getConnectionId(conn); 131 if (!mCallProtos.contains(id)) { 132 // handle new connections 133 if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) { 134 addCall(conn); 135 checkCallSetup(conn, mCallProtos.get(id)); 136 } else { 137 logd("onRilCallListChanged: skip adding disconnected connection"); 138 } 139 } else { 140 VoiceCallSession proto = mCallProtos.get(id); 141 // handle call state change 142 checkCallSetup(conn, proto); 143 // handle terminated connections 144 if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) { 145 proto.bearerAtEnd = getBearer(conn); // should be CS 146 proto.disconnectReasonCode = conn.getDisconnectCause(); 147 proto.disconnectExtraCode = conn.getPreciseDisconnectCause(); 148 proto.disconnectExtraMessage = conn.getVendorDisconnectCause(); 149 finishCall(id); 150 } 151 } 152 } 153 // NOTE: we cannot check stray connections (CS call in our list but not in RIL), as 154 // GsmCdmaCallTracker can call this with a partial list 155 } 156 157 /* IMS calls */ 158 159 /** Updates internal states when an IMS MO call is created. */ onImsDial(ImsPhoneConnection conn)160 public synchronized void onImsDial(ImsPhoneConnection conn) { 161 addCall(conn); 162 if (conn.hasRttTextStream()) { 163 setRttStarted(conn); 164 } 165 } 166 167 /** Updates internal states when an IMS MT call is created. */ onImsCallReceived(ImsPhoneConnection conn)168 public synchronized void onImsCallReceived(ImsPhoneConnection conn) { 169 addCall(conn); 170 if (conn.hasRttTextStream()) { 171 setRttStarted(conn); 172 } 173 } 174 175 /** Updates internal states when previous IMS calls are accepted to track MT call setup time. */ onImsAcceptCall(List<Connection> connections)176 public synchronized void onImsAcceptCall(List<Connection> connections) { 177 for (Connection conn : connections) { 178 addCall(conn); 179 } 180 } 181 182 /** Updates internal states when an IMS call is terminated. */ onImsCallTerminated( @ullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo)183 public synchronized void onImsCallTerminated( 184 @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) { 185 if (conn == null) { 186 List<Integer> imsConnIds = getImsConnectionIds(); 187 if (imsConnIds.size() == 1) { 188 loge("onImsCallTerminated: ending IMS call w/ conn=null"); 189 finishImsCall(imsConnIds.get(0), reasonInfo); 190 } else { 191 loge("onImsCallTerminated: %d IMS calls w/ conn=null", imsConnIds.size()); 192 } 193 } else { 194 int id = getConnectionId(conn); 195 if (mCallProtos.contains(id)) { 196 finishImsCall(id, reasonInfo); 197 } else { 198 loge("onImsCallTerminated: untracked connection"); 199 // fake a call so at least some info can be tracked 200 addCall(conn); 201 finishImsCall(id, reasonInfo); 202 } 203 } 204 } 205 206 /** Updates internal states when RTT is started on an IMS call. */ onRttStarted(ImsPhoneConnection conn)207 public synchronized void onRttStarted(ImsPhoneConnection conn) { 208 setRttStarted(conn); 209 } 210 211 /* general & misc. */ 212 213 /** Updates internal states when audio codec for a call is changed. */ onAudioCodecChanged(Connection conn, int audioQuality)214 public synchronized void onAudioCodecChanged(Connection conn, int audioQuality) { 215 VoiceCallSession proto = mCallProtos.get(getConnectionId(conn)); 216 if (proto == null) { 217 loge("onAudioCodecChanged: untracked connection"); 218 return; 219 } 220 proto.codecBitmask |= audioQualityToCodecBitmask(proto.bearerAtEnd, audioQuality); 221 } 222 223 /** 224 * Updates internal states when a call changes state to track setup time and status. 225 * 226 * <p>This is currently mainly used by IMS since CS call states are updated through {@link 227 * #onRilCallListChanged}. 228 */ onCallStateChanged(Call call)229 public synchronized void onCallStateChanged(Call call) { 230 for (Connection conn : call.getConnections()) { 231 VoiceCallSession proto = mCallProtos.get(getConnectionId(conn)); 232 if (proto != null) { 233 checkCallSetup(conn, proto); 234 } else { 235 loge("onCallStateChanged: untracked connection"); 236 } 237 } 238 } 239 240 /** Updates internal states when an IMS call is handover to a CS call. */ onRilSrvccStateChanged(int state)241 public synchronized void onRilSrvccStateChanged(int state) { 242 List<Connection> handoverConnections = null; 243 if (mPhone.getImsPhone() != null) { 244 loge("onRilSrvccStateChanged: ImsPhone is null"); 245 } else { 246 handoverConnections = mPhone.getImsPhone().getHandoverConnection(); 247 } 248 List<Integer> imsConnIds; 249 if (handoverConnections == null) { 250 imsConnIds = getImsConnectionIds(); 251 loge("onRilSrvccStateChanged: ImsPhone has no handover, we have %d", imsConnIds.size()); 252 } else { 253 imsConnIds = 254 handoverConnections.stream() 255 .map(VoiceCallSessionStats::getConnectionId) 256 .collect(Collectors.toList()); 257 } 258 switch (state) { 259 case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED: 260 // connection will now be CS 261 for (int id : imsConnIds) { 262 VoiceCallSession proto = mCallProtos.get(id); 263 proto.srvccCompleted = true; 264 proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; 265 } 266 break; 267 case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED: 268 for (int id : imsConnIds) { 269 mCallProtos.get(id).srvccFailureCount++; 270 } 271 break; 272 case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED: 273 for (int id : imsConnIds) { 274 mCallProtos.get(id).srvccCancellationCount++; 275 } 276 break; 277 default: // including STARTED and NONE, do nothing 278 } 279 } 280 281 /** Updates internal states when RAT changes. */ onServiceStateChanged(ServiceState state)282 public synchronized void onServiceStateChanged(ServiceState state) { 283 if (hasCalls()) { 284 updateRatTracker(state); 285 } 286 } 287 288 /* internal */ 289 290 /** 291 * Adds a call connection. 292 * 293 * <p>Should be called when the call is created, and when setup begins (upon {@code 294 * RilRequest.RIL_REQUEST_ANSWER} or {@code ImsCommand.IMS_CMD_ACCEPT}). 295 */ addCall(Connection conn)296 private void addCall(Connection conn) { 297 int id = getConnectionId(conn); 298 if (mCallProtos.contains(id)) { 299 // mostly handles ringing MT call getting accepted (MT call setup begins) 300 logd("addCall: resetting setup info"); 301 VoiceCallSession proto = mCallProtos.get(id); 302 proto.setupBeginMillis = getTimeMillis(); 303 proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 304 } else { 305 int bearer = getBearer(conn); 306 ServiceState serviceState = getServiceState(); 307 int rat = getRat(serviceState); 308 309 VoiceCallSession proto = new VoiceCallSession(); 310 311 proto.bearerAtStart = bearer; 312 proto.bearerAtEnd = bearer; 313 proto.direction = getDirection(conn); 314 proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN; 315 proto.setupFailed = true; 316 proto.disconnectReasonCode = conn.getDisconnectCause(); 317 proto.disconnectExtraCode = conn.getPreciseDisconnectCause(); 318 proto.disconnectExtraMessage = conn.getVendorDisconnectCause(); 319 proto.ratAtStart = rat; 320 proto.ratAtEnd = rat; 321 proto.ratSwitchCount = 0L; 322 proto.codecBitmask = 0L; 323 proto.simSlotIndex = getSimSlotId(); 324 proto.isMultiSim = SimSlotState.getCurrentState().numActiveSims > 1; 325 proto.isEsim = isEsim(); 326 proto.carrierId = mPhone.getCarrierId(); 327 proto.srvccCompleted = false; 328 proto.srvccFailureCount = 0L; 329 proto.srvccCancellationCount = 0L; 330 proto.rttEnabled = false; 331 proto.isEmergency = conn.isEmergencyCall(); 332 proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false; 333 334 // internal fields for tracking 335 proto.setupBeginMillis = getTimeMillis(); 336 337 proto.concurrentCallCountAtStart = mCallProtos.size(); 338 mCallProtos.put(id, proto); 339 340 // RAT call count needs to be updated 341 updateRatTracker(serviceState); 342 } 343 } 344 345 /** Sends the call metrics to persist storage when it is finished. */ finishCall(int connectionId)346 private void finishCall(int connectionId) { 347 VoiceCallSession proto = mCallProtos.get(connectionId); 348 if (proto == null) { 349 loge("finishCall: could not find call to be removed"); 350 return; 351 } 352 mCallProtos.delete(connectionId); 353 proto.concurrentCallCountAtEnd = mCallProtos.size(); 354 355 // ensure internal fields are cleared 356 proto.setupBeginMillis = 0L; 357 358 // sanitize for javanano & StatsEvent 359 if (proto.disconnectExtraMessage == null) { 360 proto.disconnectExtraMessage = ""; 361 } 362 363 mAtomsStorage.addVoiceCallSession(proto); 364 365 // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls) 366 if (!hasCalls()) { 367 mRatUsage.conclude(getTimeMillis()); 368 mAtomsStorage.addVoiceCallRatUsage(mRatUsage); 369 mRatUsage.clear(); 370 } 371 } 372 setRttStarted(ImsPhoneConnection conn)373 private void setRttStarted(ImsPhoneConnection conn) { 374 VoiceCallSession proto = mCallProtos.get(getConnectionId(conn)); 375 if (proto == null) { 376 loge("onRttStarted: untracked connection"); 377 return; 378 } 379 // should be IMS w/o SRVCC 380 if (proto.bearerAtStart != getBearer(conn) || proto.bearerAtEnd != getBearer(conn)) { 381 loge("onRttStarted: connection bearer mismatch but proceeding"); 382 } 383 proto.rttEnabled = true; 384 } 385 386 /** Returns a {@link Set} of Connection IDs so RAT usage can be correctly tracked. */ getConnectionIds()387 private Set<Integer> getConnectionIds() { 388 Set<Integer> ids = new HashSet<>(); 389 for (int i = 0; i < mCallProtos.size(); i++) { 390 ids.add(mCallProtos.keyAt(i)); 391 } 392 return ids; 393 } 394 getImsConnectionIds()395 private List<Integer> getImsConnectionIds() { 396 List<Integer> imsConnIds = new ArrayList<>(mCallProtos.size()); 397 for (int i = 0; i < mCallProtos.size(); i++) { 398 if (mCallProtos.valueAt(i).bearerAtEnd 399 == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) { 400 imsConnIds.add(mCallProtos.keyAt(i)); 401 } 402 } 403 return imsConnIds; 404 } 405 hasCalls()406 private boolean hasCalls() { 407 return mCallProtos.size() > 0; 408 } 409 checkCallSetup(Connection conn, VoiceCallSession proto)410 private void checkCallSetup(Connection conn, VoiceCallSession proto) { 411 if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) { 412 proto.setupDuration = classifySetupDuration(getTimeMillis() - proto.setupBeginMillis); 413 proto.setupBeginMillis = 0L; 414 } 415 // clear setupFailed if call now active, but otherwise leave it unchanged 416 if (conn.getState() == Call.State.ACTIVE) { 417 proto.setupFailed = false; 418 } 419 } 420 updateRatTracker(ServiceState state)421 private void updateRatTracker(ServiceState state) { 422 int rat = getRat(state); 423 mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds()); 424 for (int i = 0; i < mCallProtos.size(); i++) { 425 VoiceCallSession proto = mCallProtos.valueAt(i); 426 if (proto.ratAtEnd != rat) { 427 proto.ratSwitchCount++; 428 proto.ratAtEnd = rat; 429 } 430 // assuming that SIM carrier ID does not change during the call 431 } 432 } 433 finishImsCall(int id, ImsReasonInfo reasonInfo)434 private void finishImsCall(int id, ImsReasonInfo reasonInfo) { 435 VoiceCallSession proto = mCallProtos.get(id); 436 proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; 437 proto.disconnectReasonCode = reasonInfo.mCode; 438 proto.disconnectExtraCode = reasonInfo.mExtraCode; 439 proto.disconnectExtraMessage = reasonInfo.mExtraMessage; 440 finishCall(id); 441 } 442 isEsim()443 private boolean isEsim() { 444 int slotId = getSimSlotId(); 445 UiccSlot slot = mUiccController.getUiccSlot(slotId); 446 if (slot != null) { 447 return slot.isEuicc(); 448 } else { 449 // should not happen, but assume we are not using eSIM 450 loge("isEsim: slot %d is null", slotId); 451 return false; 452 } 453 } 454 getSimSlotId()455 private int getSimSlotId() { 456 // NOTE: UiccController's mapping hasn't be initialized when Phone was created 457 return mUiccController.getSlotIdFromPhoneId(mPhoneId); 458 } 459 getServiceState()460 private @Nullable ServiceState getServiceState() { 461 ServiceStateTracker tracker = mPhone.getServiceStateTracker(); 462 return tracker != null ? tracker.getServiceState() : null; 463 } 464 getDirection(Connection conn)465 private static int getDirection(Connection conn) { 466 return conn.isIncoming() 467 ? VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT 468 : VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO; 469 } 470 getBearer(Connection conn)471 private static int getBearer(Connection conn) { 472 int phoneType = conn.getPhoneType(); 473 switch (phoneType) { 474 case PhoneConstants.PHONE_TYPE_GSM: 475 case PhoneConstants.PHONE_TYPE_CDMA: 476 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS; 477 case PhoneConstants.PHONE_TYPE_IMS: 478 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS; 479 default: 480 loge("getBearer: unknown phoneType=%d", phoneType); 481 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN; 482 } 483 } 484 getRat(@ullable ServiceState state)485 private @NetworkType int getRat(@Nullable ServiceState state) { 486 if (state == null) { 487 return TelephonyManager.NETWORK_TYPE_UNKNOWN; 488 } 489 boolean isWifiCall = 490 mPhone.getImsPhone() != null 491 && mPhone.getImsPhone().isWifiCallingEnabled() 492 && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN; 493 return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType(); 494 } 495 496 // NOTE: when setup is finished for MO calls, it is not successful yet. isSetupFinished(@ullable Call call)497 private static boolean isSetupFinished(@Nullable Call call) { 498 if (call != null) { 499 switch (call.getState()) { 500 case ACTIVE: // MT setup: accepted to ACTIVE 501 case ALERTING: // MO setup: dial to ALERTING 502 return true; 503 default: // do nothing 504 } 505 } 506 return false; 507 } 508 audioQualityToCodecBitmask(int bearer, int audioQuality)509 private static long audioQualityToCodecBitmask(int bearer, int audioQuality) { 510 switch (bearer) { 511 case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS: 512 return CS_CODEC_MAP.get(audioQuality, AUDIO_CODEC_UNKNOWN); 513 case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS: 514 return IMS_CODEC_MAP.get(audioQuality, AUDIO_CODEC_UNKNOWN); 515 default: 516 loge("audioQualityToCodecBitmask: unknown bearer %d", bearer); 517 return AUDIO_CODEC_UNKNOWN; 518 } 519 } 520 classifySetupDuration(long durationMillis)521 private static int classifySetupDuration(long durationMillis) { 522 for (int i = 0; i < CALL_SETUP_DURATION_MAP.size(); i++) { 523 if (durationMillis < CALL_SETUP_DURATION_MAP.keyAt(i)) { 524 return CALL_SETUP_DURATION_MAP.valueAt(i); 525 } 526 } 527 return VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW; 528 } 529 530 /** 531 * Generates an ID for each connection, which should be the same for IMS and CS connections 532 * involved in the same SRVCC. 533 * 534 * <p>Among the fields copied from ImsPhoneConnection to GsmCdmaConnection during SRVCC, the 535 * Connection's create time seems to be the best choice for ID (assuming no multiple calls in a 536 * millisecond). The 64-bit time is truncated to 32-bit so it can be used as an index in various 537 * data structures, which is good for calls shorter than 49 days. 538 */ getConnectionId(Connection conn)539 private static int getConnectionId(Connection conn) { 540 return conn == null ? 0 : (int) conn.getCreateTime(); 541 } 542 543 @VisibleForTesting getTimeMillis()544 protected long getTimeMillis() { 545 return SystemClock.elapsedRealtime(); 546 } 547 logd(String format, Object... args)548 private static void logd(String format, Object... args) { 549 Rlog.d(TAG, String.format(format, args)); 550 } 551 loge(String format, Object... args)552 private static void loge(String format, Object... args) { 553 Rlog.e(TAG, String.format(format, args)); 554 } 555 buildGsmCdmaCodecMap()556 private static SparseLongArray buildGsmCdmaCodecMap() { 557 SparseLongArray map = new SparseLongArray(); 558 559 map.put(DriverCall.AUDIO_QUALITY_AMR, 1L << AudioCodec.AUDIO_CODEC_AMR); 560 map.put(DriverCall.AUDIO_QUALITY_AMR_WB, 1L << AudioCodec.AUDIO_CODEC_AMR_WB); 561 map.put(DriverCall.AUDIO_QUALITY_GSM_EFR, 1L << AudioCodec.AUDIO_CODEC_GSM_EFR); 562 map.put(DriverCall.AUDIO_QUALITY_GSM_FR, 1L << AudioCodec.AUDIO_CODEC_GSM_FR); 563 map.put(DriverCall.AUDIO_QUALITY_GSM_HR, 1L << AudioCodec.AUDIO_CODEC_GSM_HR); 564 map.put(DriverCall.AUDIO_QUALITY_EVRC, 1L << AudioCodec.AUDIO_CODEC_EVRC); 565 map.put(DriverCall.AUDIO_QUALITY_EVRC_B, 1L << AudioCodec.AUDIO_CODEC_EVRC_B); 566 map.put(DriverCall.AUDIO_QUALITY_EVRC_WB, 1L << AudioCodec.AUDIO_CODEC_EVRC_WB); 567 map.put(DriverCall.AUDIO_QUALITY_EVRC_NW, 1L << AudioCodec.AUDIO_CODEC_EVRC_NW); 568 569 return map; 570 } 571 buildImsCodecMap()572 private static SparseLongArray buildImsCodecMap() { 573 SparseLongArray map = new SparseLongArray(); 574 575 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR, 1L << AudioCodec.AUDIO_CODEC_AMR); 576 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB, 1L << AudioCodec.AUDIO_CODEC_AMR_WB); 577 map.put( 578 ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K, 579 1L << AudioCodec.AUDIO_CODEC_QCELP13K); 580 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC, 1L << AudioCodec.AUDIO_CODEC_EVRC); 581 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B, 1L << AudioCodec.AUDIO_CODEC_EVRC_B); 582 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB, 1L << AudioCodec.AUDIO_CODEC_EVRC_WB); 583 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW, 1L << AudioCodec.AUDIO_CODEC_EVRC_NW); 584 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR, 1L << AudioCodec.AUDIO_CODEC_GSM_EFR); 585 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR, 1L << AudioCodec.AUDIO_CODEC_GSM_FR); 586 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR, 1L << AudioCodec.AUDIO_CODEC_GSM_HR); 587 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711U, 1L << AudioCodec.AUDIO_CODEC_G711U); 588 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G723, 1L << AudioCodec.AUDIO_CODEC_G723); 589 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711A, 1L << AudioCodec.AUDIO_CODEC_G711A); 590 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G722, 1L << AudioCodec.AUDIO_CODEC_G722); 591 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711AB, 1L << AudioCodec.AUDIO_CODEC_G711AB); 592 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G729, 1L << AudioCodec.AUDIO_CODEC_G729); 593 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB, 1L << AudioCodec.AUDIO_CODEC_EVS_NB); 594 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB, 1L << AudioCodec.AUDIO_CODEC_EVS_WB); 595 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB, 1L << AudioCodec.AUDIO_CODEC_EVS_SWB); 596 map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB, 1L << AudioCodec.AUDIO_CODEC_EVS_FB); 597 598 return map; 599 } 600 buildCallSetupDurationMap()601 private static SparseIntArray buildCallSetupDurationMap() { 602 SparseIntArray map = new SparseIntArray(); 603 604 map.put(0, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN); 605 map.put(60, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST); 606 map.put(100, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST); 607 map.put(300, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST); 608 map.put(600, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST); 609 map.put(1000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL); 610 map.put(3000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW); 611 map.put(6000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW); 612 map.put(10000, VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW); 613 // anything above would be CALL_SETUP_DURATION_EXTREMELY_SLOW 614 615 return map; 616 } 617 } 618