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