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