• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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