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