• 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__CALL_DURATION__CALL_DURATION_LESS_THAN_FIVE_MINUTES;
23 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_HOUR;
24 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_MINUTE;
25 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_TEN_MINUTES;
26 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_THIRTY_MINUTES;
27 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_MORE_THAN_ONE_HOUR;
28 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
29 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
30 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
31 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND;
32 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
33 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
34 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
35 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND;
36 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT;
37 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
38 
39 import android.annotation.Nullable;
40 import android.content.Context;
41 import android.net.wifi.WifiInfo;
42 import android.net.wifi.WifiManager;
43 import android.os.SystemClock;
44 import android.telecom.VideoProfile;
45 import android.telecom.VideoProfile.VideoState;
46 import android.telephony.Annotation.NetworkType;
47 import android.telephony.AnomalyReporter;
48 import android.telephony.DisconnectCause;
49 import android.telephony.NetworkRegistrationInfo;
50 import android.telephony.PreciseDataConnectionState;
51 import android.telephony.ServiceState;
52 import android.telephony.TelephonyManager;
53 import android.telephony.data.ApnSetting;
54 import android.telephony.ims.ImsReasonInfo;
55 import android.telephony.ims.ImsStreamMediaProfile;
56 import android.util.LongSparseArray;
57 import android.util.SparseArray;
58 import android.util.SparseIntArray;
59 
60 import com.android.internal.annotations.VisibleForTesting;
61 import com.android.internal.telephony.Call;
62 import com.android.internal.telephony.Connection;
63 import com.android.internal.telephony.DriverCall;
64 import com.android.internal.telephony.GsmCdmaConnection;
65 import com.android.internal.telephony.Phone;
66 import com.android.internal.telephony.PhoneConstants;
67 import com.android.internal.telephony.PhoneFactory;
68 import com.android.internal.telephony.ServiceStateTracker;
69 import com.android.internal.telephony.imsphone.ImsPhone;
70 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
71 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
72 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
73 import com.android.internal.telephony.uicc.UiccController;
74 import com.android.telephony.Rlog;
75 
76 import java.util.ArrayList;
77 import java.util.Arrays;
78 import java.util.HashSet;
79 import java.util.List;
80 import java.util.Set;
81 import java.util.UUID;
82 import java.util.stream.Collectors;
83 
84 /** Collects voice call events per phone ID for the pulled atom. */
85 public class VoiceCallSessionStats {
86     private static final String TAG = VoiceCallSessionStats.class.getSimpleName();
87 
88     // Upper bounds of each call setup duration category in milliseconds.
89     private static final int CALL_SETUP_DURATION_UNKNOWN = 0;
90     private static final int CALL_SETUP_DURATION_EXTREMELY_FAST = 400;
91     private static final int CALL_SETUP_DURATION_ULTRA_FAST = 700;
92     private static final int CALL_SETUP_DURATION_VERY_FAST = 1000;
93     private static final int CALL_SETUP_DURATION_FAST = 1500;
94     private static final int CALL_SETUP_DURATION_NORMAL = 2500;
95     private static final int CALL_SETUP_DURATION_SLOW = 4000;
96     private static final int CALL_SETUP_DURATION_VERY_SLOW = 6000;
97     private static final int CALL_SETUP_DURATION_ULTRA_SLOW = 10000;
98     // CALL_SETUP_DURATION_EXTREMELY_SLOW has no upper bound (it includes everything above 10000)
99 
100     // Upper bounds of each call duration category in milliseconds.
101     private static final int CALL_DURATION_ONE_MINUTE = 60000;
102     private static final int CALL_DURATION_FIVE_MINUTES = 300000;
103     private static final int CALL_DURATION_TEN_MINUTES = 600000;
104     private static final int CALL_DURATION_THIRTY_MINUTES = 1800000;
105     private static final int CALL_DURATION_ONE_HOUR = 3600000;
106 
107     /** Number of buckets for codec quality, from UNKNOWN to FULLBAND. */
108     private static final int CODEC_QUALITY_COUNT = 5;
109 
110     /**
111      * Threshold to calculate the main audio codec quality of the call.
112      *
113      * <p>The audio codec quality was equal to or greater than the main audio codec quality for at
114      * least 70% of the call.
115      */
116     private static final int MAIN_CODEC_QUALITY_THRESHOLD = 70;
117 
118     /** Holds the audio codec value for CS calls. */
119     private static final SparseIntArray CS_CODEC_MAP = buildGsmCdmaCodecMap();
120 
121     /** Holds the audio codec value for IMS calls. */
122     private static final SparseIntArray IMS_CODEC_MAP = buildImsCodecMap();
123 
124     /** Holds call duration buckets with values as their upper bounds in milliseconds. */
125     private static final SparseIntArray CALL_DURATION_MAP = buildCallDurationMap();
126 
127     /** UUID for reporting concurrent call anomaly */
128     private static final UUID CONCURRENT_CALL_ANOMALY_UUID =
129             UUID.fromString("76780b5a-623e-48a4-be3f-925e05177c9c");
130 
131     /** If the number of concurrent calls exceeds this number, report anomaly*/
132     private static final int MAX_NORMAL_CONCURRENT_CALLS = 3;
133 
134     /**
135      * Tracks statistics for each call connection, indexed with ID returned by {@link
136      * #getConnectionId}.
137      */
138     private final SparseArray<VoiceCallSession> mCallProtos = new SparseArray<>();
139 
140     /**
141      * Tracks usage of codecs for each call.
142      *
143      * <p>The outer array is used to map each connection id to the corresponding codec usage. The
144      * inner array is used to map timestamp (key) with the codec in use (value).
145      */
146     private final SparseArray<LongSparseArray<Integer>> mCodecUsage = new SparseArray<>();
147 
148     /**
149      * Tracks call RAT usage.
150      *
151      * <p>RAT usage is mainly tied to phones rather than calls, since each phone can have multiple
152      * concurrent calls, and we do not want to count the RAT duration multiple times.
153      */
154     private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker();
155 
156     private final int mPhoneId;
157     private final Phone mPhone;
158 
159     private final PersistAtomsStorage mAtomsStorage =
160             PhoneFactory.getMetricsCollector().getAtomsStorage();
161     private final UiccController mUiccController = UiccController.getInstance();
162     private final DeviceStateHelper mDeviceStateHelper =
163             PhoneFactory.getMetricsCollector().getDeviceStateHelper();
164 
VoiceCallSessionStats(int phoneId, Phone phone)165     public VoiceCallSessionStats(int phoneId, Phone phone) {
166         mPhoneId = phoneId;
167         mPhone = phone;
168 
169         DataConnectionStateTracker.getInstance(phoneId).start(phone);
170     }
171 
172     /* CS calls */
173 
174     /** Updates internal states when previous CS calls are accepted to track MT call setup time. */
onRilAcceptCall(List<Connection> connections)175     public synchronized void onRilAcceptCall(List<Connection> connections) {
176         for (Connection conn : connections) {
177             acceptCall(conn);
178         }
179     }
180 
181     /** Updates internal states when a CS MO call is created. */
onRilDial(Connection conn)182     public synchronized void onRilDial(Connection conn) {
183         addCall(conn);
184     }
185 
186     /**
187      * Updates internal states when CS calls are created or terminated, or CS call state is changed.
188      */
onRilCallListChanged(List<GsmCdmaConnection> connections)189     public synchronized void onRilCallListChanged(List<GsmCdmaConnection> connections) {
190         for (Connection conn : connections) {
191             int id = getConnectionId(conn);
192             if (!mCallProtos.contains(id)) {
193                 // handle new connections
194                 if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
195                     addCall(conn);
196                     checkCallSetup(conn, mCallProtos.get(id));
197                 } else {
198                     logd(
199                             "onRilCallListChanged: skip adding disconnected connection,"
200                                     + " connectionId=%d",
201                             id);
202                 }
203             } else {
204                 VoiceCallSession proto = mCallProtos.get(id);
205                 // handle call state change
206                 checkCallSetup(conn, proto);
207                 // handle terminated connections
208                 if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
209                     proto.bearerAtEnd = getBearer(conn); // should be CS
210                     proto.disconnectReasonCode = conn.getDisconnectCause();
211                     proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
212                     proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
213                     proto.callDuration = classifyCallDuration(conn.getDurationMillis());
214                     finishCall(id);
215                 }
216             }
217         }
218         // NOTE: we cannot check stray connections (CS call in our list but not in RIL), as
219         // GsmCdmaCallTracker can call this with a partial list
220     }
221 
222     /* IMS calls */
223 
224     /** Updates internal states when an IMS MO call is created. */
onImsDial(ImsPhoneConnection conn)225     public synchronized void onImsDial(ImsPhoneConnection conn) {
226         addCall(conn);
227         if (conn.hasRttTextStream()) {
228             setRttStarted(conn);
229         }
230     }
231 
232     /** Updates internal states when an IMS MT call is created. */
onImsCallReceived(ImsPhoneConnection conn)233     public synchronized void onImsCallReceived(ImsPhoneConnection conn) {
234         addCall(conn);
235         if (conn.hasRttTextStream()) {
236             setRttStarted(conn);
237         }
238     }
239 
240     /** Updates internal states when previous IMS calls are accepted to track MT call setup time. */
onImsAcceptCall(List<Connection> connections)241     public synchronized void onImsAcceptCall(List<Connection> connections) {
242         for (Connection conn : connections) {
243             acceptCall(conn);
244         }
245     }
246 
247     /** Updates internal states when an IMS fails to start. */
onImsCallStartFailed( @ullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo)248     public synchronized void onImsCallStartFailed(
249             @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) {
250         onImsCallTerminated(conn, reasonInfo);
251     }
252 
253     /** Updates internal states when an IMS call is terminated. */
onImsCallTerminated( @ullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo)254     public synchronized void onImsCallTerminated(
255             @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) {
256         if (conn == null) {
257             List<Integer> imsConnIds = getImsConnectionIds();
258             if (imsConnIds.size() == 1) {
259                 loge("onImsCallTerminated: ending IMS call w/ conn=null");
260                 finishImsCall(imsConnIds.get(0), reasonInfo, 0);
261             } else {
262                 loge("onImsCallTerminated: %d IMS calls w/ conn=null", imsConnIds.size());
263             }
264         } else {
265             int id = getConnectionId(conn);
266             if (mCallProtos.contains(id)) {
267                 finishImsCall(id, reasonInfo, conn.getDurationMillis());
268             } else {
269                 loge("onImsCallTerminated: untracked connection, connectionId=%d", id);
270                 // fake a call so at least some info can be tracked
271                 addCall(conn);
272                 finishImsCall(id, reasonInfo, conn.getDurationMillis());
273             }
274         }
275     }
276 
277     /** Updates internal states when RTT is started on an IMS call. */
onRttStarted(ImsPhoneConnection conn)278     public synchronized void onRttStarted(ImsPhoneConnection conn) {
279         setRttStarted(conn);
280     }
281 
282     /* general & misc. */
283 
284     /** Updates internal states when audio codec for a call is changed. */
onAudioCodecChanged(Connection conn, int audioQuality)285     public synchronized void onAudioCodecChanged(Connection conn, int audioQuality) {
286         int id = getConnectionId(conn);
287         VoiceCallSession proto = mCallProtos.get(id);
288         if (proto == null) {
289             loge("onAudioCodecChanged: untracked connection, connectionId=%d", id);
290             return;
291         }
292         int codec = audioQualityToCodec(proto.bearerAtEnd, audioQuality);
293         proto.codecBitmask |= (1L << codec);
294 
295         if (mCodecUsage.contains(id)) {
296             mCodecUsage.get(id).append(getTimeMillis(), codec);
297         } else {
298             LongSparseArray<Integer> arr = new LongSparseArray<>();
299             arr.append(getTimeMillis(), codec);
300             mCodecUsage.put(id, arr);
301         }
302     }
303 
304     /** Updates internal states when video state changes. */
onVideoStateChange( ImsPhoneConnection conn, @VideoState int videoState)305     public synchronized void onVideoStateChange(
306             ImsPhoneConnection conn, @VideoState int videoState) {
307         int id = getConnectionId(conn);
308         VoiceCallSession proto = mCallProtos.get(id);
309         if (proto == null) {
310             loge("onVideoStateChange: untracked connection, connectionId=%d", id);
311             return;
312         }
313         logd("onVideoStateChange: video state=%d, connectionId=%d", videoState, id);
314         if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
315             proto.videoEnabled = true;
316         }
317     }
318 
319     /** Updates internal states when multiparty state changes. */
onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty)320     public synchronized void onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty) {
321         int id = getConnectionId(conn);
322         VoiceCallSession proto = mCallProtos.get(id);
323         if (proto == null) {
324             loge("onMultipartyChange: untracked connection, connectionId=%d", id);
325             return;
326         }
327         logd("onMultipartyChange: isMultiparty=%b, connectionId=%d", isMultiParty, id);
328         if (isMultiParty) {
329             proto.isMultiparty = true;
330         }
331     }
332 
333     /**
334      * Updates internal states when a call changes state to track setup time and status.
335      *
336      * <p>This is currently mainly used by IMS since CS call states are updated through {@link
337      * #onRilCallListChanged}.
338      */
onCallStateChanged(Call call)339     public synchronized void onCallStateChanged(Call call) {
340         for (Connection conn : call.getConnections()) {
341             int id = getConnectionId(conn);
342             VoiceCallSession proto = mCallProtos.get(id);
343             if (proto != null) {
344                 checkCallSetup(conn, proto);
345             } else {
346                 loge("onCallStateChanged: untracked connection, connectionId=%d", id);
347             }
348         }
349     }
350 
351     /** Updates internal states when an IMS call is handover to a CS call. */
onRilSrvccStateChanged(int state)352     public synchronized void onRilSrvccStateChanged(int state) {
353         List<Connection> handoverConnections = null;
354         if (mPhone.getImsPhone() != null) {
355             handoverConnections = mPhone.getImsPhone().getHandoverConnection();
356         } else {
357             loge("onRilSrvccStateChanged: ImsPhone is null");
358         }
359         List<Integer> imsConnIds;
360         if (handoverConnections == null) {
361             imsConnIds = getImsConnectionIds();
362             loge("onRilSrvccStateChanged: ImsPhone has no handover, we have %d", imsConnIds.size());
363         } else {
364             imsConnIds =
365                     handoverConnections.stream()
366                             .map(VoiceCallSessionStats::getConnectionId)
367                             .collect(Collectors.toList());
368         }
369         switch (state) {
370             case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
371                 // connection will now be CS
372                 for (int id : imsConnIds) {
373                     VoiceCallSession proto = mCallProtos.get(id);
374                     proto.srvccCompleted = true;
375                     proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
376                     // Call RAT may have changed (e.g. IWLAN -> UMTS) due to bearer change
377                     updateRatAtEnd(proto, getVoiceRatWithVoNRFix(
378                             mPhone, mPhone.getServiceState(), proto.bearerAtEnd));
379                 }
380                 break;
381             case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
382                 for (int id : imsConnIds) {
383                     mCallProtos.get(id).srvccFailureCount++;
384                 }
385                 break;
386             case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
387                 for (int id : imsConnIds) {
388                     mCallProtos.get(id).srvccCancellationCount++;
389                 }
390                 break;
391             default: // including STARTED and NONE, do nothing
392         }
393     }
394 
395     /** Updates internal states when RAT changes. */
onServiceStateChanged(ServiceState state)396     public synchronized void onServiceStateChanged(ServiceState state) {
397         if (hasCalls()) {
398             updateRatTracker(state);
399         }
400     }
401 
402     /** Updates internal states when IMS/Emergency PDN/PDU state changes */
onPreciseDataConnectionStateChanged( PreciseDataConnectionState connectionState)403     public synchronized void onPreciseDataConnectionStateChanged(
404             PreciseDataConnectionState connectionState) {
405         if (hasCalls()) {
406             updateVoiceCallSessionBearerState(connectionState);
407         }
408     }
409 
410     /* internal */
411 
412     /** Handles ringing MT call getting accepted. */
acceptCall(Connection conn)413     private void acceptCall(Connection conn) {
414         int id = getConnectionId(conn);
415         if (mCallProtos.contains(id)) {
416             logd("acceptCall: resetting setup info, connectionId=%d", id);
417             VoiceCallSession proto = mCallProtos.get(id);
418             proto.setupBeginMillis = getTimeMillis();
419         } else {
420             loge("acceptCall: untracked connection, connectionId=%d", id);
421         }
422     }
423 
424     /**
425      * Adds a call connection.
426      *
427      * <p>Should be called when the call is created, and when setup begins (upon {@code
428      * RilRequest.RIL_REQUEST_ANSWER} or {@code ImsCommand.IMS_CMD_ACCEPT}).
429      */
addCall(Connection conn)430     private void addCall(Connection conn) {
431         int id = getConnectionId(conn);
432         if (mCallProtos.contains(id)) {
433             loge(
434                     "addCall: already tracked connection, connectionId=%d, connectionInfo=%s",
435                     id, conn);
436             return;
437         }
438         int bearer = getBearer(conn);
439         ServiceState serviceState = getServiceState();
440         @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, serviceState, bearer);
441         @VideoState int videoState = conn.getVideoState();
442         VoiceCallSession proto = new VoiceCallSession();
443 
444         proto.bearerAtStart = bearer;
445         proto.bearerAtEnd = bearer;
446         proto.direction = getDirection(conn);
447         proto.setupFailed = true;
448         proto.disconnectReasonCode = conn.getDisconnectCause();
449         proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
450         proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
451         proto.ratAtStart = rat;
452         proto.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
453         proto.ratAtEnd = rat;
454         proto.ratSwitchCount = 0L;
455         proto.ratSwitchCountAfterConnected = 0L;
456         proto.codecBitmask = 0L;
457         proto.simSlotIndex = mPhoneId;
458         proto.isMultiSim = SimSlotState.isMultiSim();
459         proto.isEsim = SimSlotState.isEsim(mPhoneId);
460         proto.carrierId = mPhone.getCarrierId();
461         proto.srvccCompleted = false;
462         proto.srvccFailureCount = 0L;
463         proto.srvccCancellationCount = 0L;
464         proto.rttEnabled = false;
465         proto.isEmergency = conn.isEmergencyCall() || conn.isNetworkIdentifiedEmergencyCall();
466         proto.isRoaming = ServiceStateStats.isNetworkRoaming(serviceState);
467         proto.isMultiparty = conn.isMultiparty();
468         proto.lastKnownRat = rat;
469         proto.videoEnabled = videoState != VideoProfile.STATE_AUDIO_ONLY ? true : false;
470         proto.handoverInProgress = isHandoverInProgress(bearer, proto.isEmergency);
471 
472         // internal fields for tracking
473         if (getDirection(conn) == VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT) {
474             // MT call setup hasn't begun hence set to 0
475             proto.setupBeginMillis = 0L;
476         } else {
477             proto.setupBeginMillis = getTimeMillis();
478         }
479 
480         // audio codec might have already been set
481         int codec = audioQualityToCodec(bearer, conn.getAudioCodec());
482         if (codec != AudioCodec.AUDIO_CODEC_UNKNOWN) {
483             proto.codecBitmask = (1L << codec);
484         }
485 
486         proto.concurrentCallCountAtStart = mCallProtos.size();
487         if (proto.concurrentCallCountAtStart > MAX_NORMAL_CONCURRENT_CALLS) {
488             AnomalyReporter.reportAnomaly(
489                     CONCURRENT_CALL_ANOMALY_UUID, "Anomalous number of concurrent calls");
490         }
491         mCallProtos.put(id, proto);
492 
493         // RAT call count needs to be updated
494         updateRatTracker(serviceState);
495     }
496 
497     /** Sends the call metrics to persist storage when it is finished. */
finishCall(int connectionId)498     private void finishCall(int connectionId) {
499         VoiceCallSession proto = mCallProtos.get(connectionId);
500         if (proto == null) {
501             loge("finishCall: could not find call to be removed, connectionId=%d", connectionId);
502             return;
503         }
504 
505         // Compute time it took to fail setup (except for MT calls that have never been picked up)
506         if (proto.setupFailed && proto.setupBeginMillis != 0L && proto.setupDurationMillis == 0) {
507             proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis);
508         }
509 
510         mCallProtos.delete(connectionId);
511         proto.concurrentCallCountAtEnd = mCallProtos.size();
512 
513         // Calculate signal strength at the end of the call
514         proto.signalStrengthAtEnd = getSignalStrength(proto.ratAtEnd);
515 
516         // Calculate main codec quality
517         proto.mainCodecQuality = finalizeMainCodecQuality(connectionId);
518 
519         // ensure internal fields are cleared
520         proto.setupBeginMillis = 0L;
521 
522         // sanitize for javanano & StatsEvent
523         if (proto.disconnectExtraMessage == null) {
524             proto.disconnectExtraMessage = "";
525         }
526 
527         // Retry populating carrier ID if it was invalid
528         if (proto.carrierId <= 0) {
529             proto.carrierId = mPhone.getCarrierId();
530         }
531 
532         // Update end RAT
533         updateRatAtEnd(proto, getVoiceRatWithVoNRFix(mPhone, getServiceState(), proto.bearerAtEnd));
534 
535         // Set device fold state
536         proto.foldState = mDeviceStateHelper.getFoldState();
537 
538         mAtomsStorage.addVoiceCallSession(proto);
539 
540         // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls)
541         if (!hasCalls()) {
542             mRatUsage.conclude(getTimeMillis());
543             mAtomsStorage.addVoiceCallRatUsage(mRatUsage);
544             mRatUsage.clear();
545         }
546     }
547 
setRttStarted(ImsPhoneConnection conn)548     private void setRttStarted(ImsPhoneConnection conn) {
549         int id = getConnectionId(conn);
550         VoiceCallSession proto = mCallProtos.get(id);
551         if (proto == null) {
552             loge("onRttStarted: untracked connection, connectionId=%d", id);
553             return;
554         }
555         // should be IMS w/o SRVCC
556         if (proto.bearerAtStart != getBearer(conn) || proto.bearerAtEnd != getBearer(conn)) {
557             loge("onRttStarted: connection bearer mismatch but proceeding, connectionId=%d", id);
558         }
559         proto.rttEnabled = true;
560     }
561 
562     /** Returns a {@link Set} of Connection IDs so RAT usage can be correctly tracked. */
getConnectionIds()563     private Set<Integer> getConnectionIds() {
564         Set<Integer> ids = new HashSet<>();
565         for (int i = 0; i < mCallProtos.size(); i++) {
566             ids.add(mCallProtos.keyAt(i));
567         }
568         return ids;
569     }
570 
getImsConnectionIds()571     private List<Integer> getImsConnectionIds() {
572         List<Integer> imsConnIds = new ArrayList<>(mCallProtos.size());
573         for (int i = 0; i < mCallProtos.size(); i++) {
574             if (mCallProtos.valueAt(i).bearerAtEnd
575                     == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
576                 imsConnIds.add(mCallProtos.keyAt(i));
577             }
578         }
579         return imsConnIds;
580     }
581 
hasCalls()582     private boolean hasCalls() {
583         return mCallProtos.size() > 0;
584     }
585 
checkCallSetup(Connection conn, VoiceCallSession proto)586     private void checkCallSetup(Connection conn, VoiceCallSession proto) {
587         if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) {
588             proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis);
589             proto.setupBeginMillis = 0L;
590         }
591         // Clear setupFailed if call now active, but otherwise leave it unchanged
592         // This block is executed only once, when call becomes active for the first time.
593         if (proto.setupFailed && conn.getState() == Call.State.ACTIVE) {
594             proto.setupFailed = false;
595             // Track RAT when voice call is connected.
596             ServiceState serviceState = getServiceState();
597             proto.ratAtConnected = getVoiceRatWithVoNRFix(mPhone, serviceState, proto.bearerAtEnd);
598             // Reset list of codecs with the last codec at the present time. In this way, we
599             // track codec quality only after call is connected and not while ringing.
600             resetCodecList(conn);
601         }
602     }
603 
updateRatTracker(ServiceState state)604     private void updateRatTracker(ServiceState state) {
605         // RAT usage is not broken down by bearer. In case a CS call is made while there is IMS
606         // voice registration, this may be inaccurate (i.e. there could be multiple RAT in use, but
607         // we only pick the most feasible one).
608         @NetworkType int rat = getVoiceRatWithVoNRFix(mPhone, state,
609                 VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN);
610         mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds());
611 
612         for (int i = 0; i < mCallProtos.size(); i++) {
613             VoiceCallSession proto = mCallProtos.valueAt(i);
614             rat = getVoiceRatWithVoNRFix(mPhone, state, proto.bearerAtEnd);
615             updateRatAtEnd(proto, rat);
616             proto.bandAtEnd = (rat == TelephonyManager.NETWORK_TYPE_IWLAN)
617                             ? 0
618                             : ServiceStateStats.getBand(state);
619             // assuming that SIM carrier ID does not change during the call
620         }
621     }
622 
updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat)623     private void updateRatAtEnd(VoiceCallSession proto, @NetworkType int rat) {
624         if (proto.ratAtEnd != rat) {
625             proto.ratSwitchCount++;
626             if (!proto.setupFailed) {
627                 proto.ratSwitchCountAfterConnected++;
628             }
629             proto.ratAtEnd = rat;
630             if (rat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
631                 proto.lastKnownRat = rat;
632             }
633         }
634     }
635 
finishImsCall(int id, ImsReasonInfo reasonInfo, long durationMillis)636     private void finishImsCall(int id, ImsReasonInfo reasonInfo, long durationMillis) {
637         VoiceCallSession proto = mCallProtos.get(id);
638         proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
639         proto.disconnectReasonCode = reasonInfo.mCode;
640         proto.disconnectExtraCode = reasonInfo.mExtraCode;
641         proto.disconnectExtraMessage = ImsStats.filterExtraMessage(reasonInfo.mExtraMessage);
642         proto.callDuration = classifyCallDuration(durationMillis);
643         finishCall(id);
644     }
645 
getServiceState()646     private @Nullable ServiceState getServiceState() {
647         ServiceStateTracker tracker = mPhone.getServiceStateTracker();
648         return tracker != null ? tracker.getServiceState() : null;
649     }
650 
getDirection(Connection conn)651     private static int getDirection(Connection conn) {
652         return conn.isIncoming()
653                 ? VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT
654                 : VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
655     }
656 
getBearer(Connection conn)657     private static int getBearer(Connection conn) {
658         int phoneType = conn.getPhoneType();
659         switch (phoneType) {
660             case PhoneConstants.PHONE_TYPE_GSM:
661             case PhoneConstants.PHONE_TYPE_CDMA:
662                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
663             case PhoneConstants.PHONE_TYPE_IMS:
664                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
665             default:
666                 loge("getBearer: unknown phoneType=%d", phoneType);
667                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
668         }
669     }
670 
671     /** Returns the signal strength. */
getSignalStrength(@etworkType int rat)672     private int getSignalStrength(@NetworkType int rat) {
673         if (rat == TelephonyManager.NETWORK_TYPE_IWLAN) {
674             return getSignalStrengthWifi();
675         } else {
676             return getSignalStrengthCellular();
677         }
678     }
679 
680     /** Returns the signal strength of WiFi. */
getSignalStrengthWifi()681     private int getSignalStrengthWifi() {
682         WifiManager wifiManager =
683                 (WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE);
684         WifiInfo wifiInfo = wifiManager.getConnectionInfo();
685         int result = VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
686         if (wifiInfo != null) {
687             int level = wifiManager.calculateSignalLevel(wifiInfo.getRssi());
688             int max = wifiManager.getMaxSignalLevel();
689             // Scale result into 0 to 4 range.
690             result =
691                     VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT * level / max;
692             logd("WiFi level: " + result + " (" + level + "/" + max + ")");
693         }
694         return result;
695     }
696 
697     /** Returns the signal strength of cellular RAT. */
getSignalStrengthCellular()698     private int getSignalStrengthCellular() {
699         return mPhone.getSignalStrength().getLevel();
700     }
701 
isHandoverInProgress(int bearer, boolean isEmergency)702     private boolean isHandoverInProgress(int bearer, boolean isEmergency) {
703         // If the call is not IMS, the bearer will not be able to handover
704         if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
705             return false;
706         }
707 
708         int apnType = isEmergency ? ApnSetting.TYPE_EMERGENCY : ApnSetting.TYPE_IMS;
709         int dataState = DataConnectionStateTracker.getInstance(mPhoneId).getDataState(apnType);
710         return dataState == TelephonyManager.DATA_HANDOVER_IN_PROGRESS;
711     }
712 
713     /**
714      * This is a copy of ServiceStateStats.getVoiceRat(Phone, ServiceState, int) with minimum fix
715      * required for tracking EPSFB correctly.
716      */
getVoiceRatWithVoNRFix( Phone phone, @Nullable ServiceState state, int bearer)717     @VisibleForTesting private static @NetworkType int getVoiceRatWithVoNRFix(
718             Phone phone, @Nullable ServiceState state, int bearer) {
719         if (state == null) {
720             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
721         }
722         ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
723         if (bearer != VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS && imsPhone != null) {
724             @NetworkType int imsVoiceRat = imsPhone.getImsStats().getImsVoiceRadioTech();
725             @NetworkType int wwanPsRat =
726                     ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_PS);
727             if (imsVoiceRat != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
728                 // If IMS is registered over WWAN but WWAN PS is not in service,
729                 // fallback to WWAN CS RAT
730                 boolean isImsVoiceRatValid =
731                         (imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
732                                 || wwanPsRat != TelephonyManager.NETWORK_TYPE_UNKNOWN);
733                 if (isImsVoiceRatValid) {
734                     // Fix for VoNR and EPSFB, b/277906557
735                     @NetworkType int oldRat = ServiceStateStats.getVoiceRat(phone, state, bearer),
736                             rat = imsVoiceRat == TelephonyManager.NETWORK_TYPE_IWLAN
737                                     ? imsVoiceRat : wwanPsRat;
738                     logd("getVoiceRatWithVoNRFix: oldRat=%d, newRat=%d", oldRat, rat);
739                     return rat;
740                 }
741             }
742         }
743         if (bearer == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
744             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
745         } else {
746             return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS);
747         }
748     }
749 
750     /** Resets the list of codecs used for the connection with only the codec currently in use. */
resetCodecList(Connection conn)751     private void resetCodecList(Connection conn) {
752         int id = getConnectionId(conn);
753         LongSparseArray<Integer> codecUsage = mCodecUsage.get(id);
754         if (codecUsage != null) {
755             int lastCodec = codecUsage.valueAt(codecUsage.size() - 1);
756             LongSparseArray<Integer> arr = new LongSparseArray<>();
757             arr.append(getTimeMillis(), lastCodec);
758             mCodecUsage.put(id, arr);
759         }
760     }
761 
762     /** Returns the main codec quality used during the call. */
finalizeMainCodecQuality(int connectionId)763     private int finalizeMainCodecQuality(int connectionId) {
764         // Retrieve information about codec usage for this call and remove it from main array.
765         if (!mCodecUsage.contains(connectionId)) {
766             return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
767         }
768         LongSparseArray<Integer> codecUsage = mCodecUsage.get(connectionId);
769         mCodecUsage.delete(connectionId);
770 
771         // Append fake entry at the end, to facilitate the calculation of time for each codec.
772         codecUsage.put(getTimeMillis(), AudioCodec.AUDIO_CODEC_UNKNOWN);
773 
774         // Calculate array with time for each quality
775         int totalTime = 0;
776         long[] timePerQuality = new long[CODEC_QUALITY_COUNT];
777         for (int i = 0; i < codecUsage.size() - 1; i++) {
778             long time = codecUsage.keyAt(i + 1) - codecUsage.keyAt(i);
779             int quality = getCodecQuality(codecUsage.valueAt(i));
780             timePerQuality[quality] += time;
781             totalTime += time;
782         }
783         logd("Time per codec quality = " + Arrays.toString(timePerQuality));
784 
785         // We calculate 70% duration of the call as the threshold for the main audio codec quality
786         // and iterate on all codec qualities. As soon as the sum of codec duration is greater than
787         // the threshold, we have identified the main codec quality.
788         long timeAtMinimumQuality = 0;
789         long timeThreshold = totalTime * MAIN_CODEC_QUALITY_THRESHOLD / 100;
790         for (int i = CODEC_QUALITY_COUNT - 1; i >= 0; i--) {
791             timeAtMinimumQuality += timePerQuality[i];
792             if (timeAtMinimumQuality >= timeThreshold) {
793                 return i;
794             }
795         }
796         return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
797     }
798 
getCodecQuality(int codec)799     private int getCodecQuality(int codec) {
800         switch (codec) {
801             case AudioCodec.AUDIO_CODEC_AMR:
802             case AudioCodec.AUDIO_CODEC_QCELP13K:
803             case AudioCodec.AUDIO_CODEC_EVRC:
804             case AudioCodec.AUDIO_CODEC_EVRC_B:
805             case AudioCodec.AUDIO_CODEC_EVRC_NW:
806             case AudioCodec.AUDIO_CODEC_GSM_EFR:
807             case AudioCodec.AUDIO_CODEC_GSM_FR:
808             case AudioCodec.AUDIO_CODEC_GSM_HR:
809             case AudioCodec.AUDIO_CODEC_G711U:
810             case AudioCodec.AUDIO_CODEC_G723:
811             case AudioCodec.AUDIO_CODEC_G711A:
812             case AudioCodec.AUDIO_CODEC_G722:
813             case AudioCodec.AUDIO_CODEC_G711AB:
814             case AudioCodec.AUDIO_CODEC_G729:
815             case AudioCodec.AUDIO_CODEC_EVS_NB:
816                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
817             case AudioCodec.AUDIO_CODEC_AMR_WB:
818             case AudioCodec.AUDIO_CODEC_EVS_WB:
819             case AudioCodec.AUDIO_CODEC_EVRC_WB:
820                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND;
821             case AudioCodec.AUDIO_CODEC_EVS_SWB:
822                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
823             case AudioCodec.AUDIO_CODEC_EVS_FB:
824                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND;
825             default:
826                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
827         }
828     }
829 
isSetupFinished(@ullable Call call)830     private static boolean isSetupFinished(@Nullable Call call) {
831         // NOTE: when setup is finished for MO calls, it is not successful yet.
832         if (call != null) {
833             switch (call.getState()) {
834                 case ACTIVE: // MT setup: accepted to ACTIVE
835                 case ALERTING: // MO setup: dial to ALERTING
836                     return true;
837                 default: // do nothing
838             }
839         }
840         return false;
841     }
842 
audioQualityToCodec(int bearer, int audioQuality)843     private static int audioQualityToCodec(int bearer, int audioQuality) {
844         switch (bearer) {
845             case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS:
846                 return CS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN);
847             case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS:
848                 return IMS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN);
849             default:
850                 loge("audioQualityToCodec: unknown bearer %d", bearer);
851                 return AudioCodec.AUDIO_CODEC_UNKNOWN;
852         }
853     }
854 
classifyCallDuration(long durationMillis)855     private static int classifyCallDuration(long durationMillis) {
856         if (durationMillis == 0L) {
857             return VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
858         }
859         // keys in CALL_SETUP_DURATION_MAP are upper bounds in ascending order
860         for (int i = 0; i < CALL_DURATION_MAP.size(); i++) {
861             if (durationMillis < CALL_DURATION_MAP.keyAt(i)) {
862                 return CALL_DURATION_MAP.valueAt(i);
863             }
864         }
865         return VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_MORE_THAN_ONE_HOUR;
866     }
867 
868     /**
869      * Generates an ID for each connection, which should be the same for IMS and CS connections
870      * involved in the same SRVCC.
871      *
872      * <p>Among the fields copied from ImsPhoneConnection to GsmCdmaConnection during SRVCC, the
873      * Connection's create time seems to be the best choice for ID (assuming no multiple calls in a
874      * millisecond). The 64-bit time is truncated to 32-bit so it can be used as an index in various
875      * data structures, which is good for calls shorter than 49 days.
876      */
getConnectionId(Connection conn)877     private static int getConnectionId(Connection conn) {
878         return conn == null ? 0 : (int) conn.getCreateTime();
879     }
880 
881     @VisibleForTesting
getTimeMillis()882     protected long getTimeMillis() {
883         return SystemClock.elapsedRealtime();
884     }
885 
logd(String format, Object... args)886     private static void logd(String format, Object... args) {
887         Rlog.d(TAG, String.format(format, args));
888     }
889 
loge(String format, Object... args)890     private static void loge(String format, Object... args) {
891         Rlog.e(TAG, String.format(format, args));
892     }
893 
buildGsmCdmaCodecMap()894     private static SparseIntArray buildGsmCdmaCodecMap() {
895         SparseIntArray map = new SparseIntArray();
896         map.put(DriverCall.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR);
897         map.put(DriverCall.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB);
898         map.put(DriverCall.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR);
899         map.put(DriverCall.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR);
900         map.put(DriverCall.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR);
901         map.put(DriverCall.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC);
902         map.put(DriverCall.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B);
903         map.put(DriverCall.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB);
904         map.put(DriverCall.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW);
905         return map;
906     }
907 
buildImsCodecMap()908     private static SparseIntArray buildImsCodecMap() {
909         SparseIntArray map = new SparseIntArray();
910         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR);
911         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB);
912         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K, AudioCodec.AUDIO_CODEC_QCELP13K);
913         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC);
914         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B);
915         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB);
916         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW);
917         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR);
918         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR);
919         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR);
920         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711U, AudioCodec.AUDIO_CODEC_G711U);
921         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G723, AudioCodec.AUDIO_CODEC_G723);
922         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711A, AudioCodec.AUDIO_CODEC_G711A);
923         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G722, AudioCodec.AUDIO_CODEC_G722);
924         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711AB, AudioCodec.AUDIO_CODEC_G711AB);
925         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G729, AudioCodec.AUDIO_CODEC_G729);
926         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB, AudioCodec.AUDIO_CODEC_EVS_NB);
927         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB, AudioCodec.AUDIO_CODEC_EVS_WB);
928         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB, AudioCodec.AUDIO_CODEC_EVS_SWB);
929         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB, AudioCodec.AUDIO_CODEC_EVS_FB);
930         return map;
931     }
932 
buildCallDurationMap()933     private static SparseIntArray buildCallDurationMap() {
934         SparseIntArray map = new SparseIntArray();
935 
936         map.put(
937                 CALL_DURATION_ONE_MINUTE,
938                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_MINUTE);
939         map.put(
940                 CALL_DURATION_FIVE_MINUTES,
941                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_FIVE_MINUTES);
942         map.put(
943                 CALL_DURATION_TEN_MINUTES,
944                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_TEN_MINUTES);
945         map.put(
946                 CALL_DURATION_THIRTY_MINUTES,
947                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_THIRTY_MINUTES);
948         map.put(
949                 CALL_DURATION_ONE_HOUR,
950                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_HOUR);
951         // anything above would be MORE_THAN_ONE_HOUR
952 
953         return map;
954     }
955 
updateVoiceCallSessionBearerState(PreciseDataConnectionState connectionState)956     private void updateVoiceCallSessionBearerState(PreciseDataConnectionState connectionState) {
957         ApnSetting apnSetting = connectionState.getApnSetting();
958         if (apnSetting == null) {
959             return;
960         }
961 
962         int apnTypes = apnSetting.getApnTypeBitmask();
963         if ((apnTypes & ApnSetting.TYPE_IMS) == 0
964                 && (apnTypes & ApnSetting.TYPE_EMERGENCY) == 0) {
965             return;
966         }
967 
968         for (int i = 0; i < mCallProtos.size(); i++) {
969             VoiceCallSession proto = mCallProtos.valueAt(i);
970             if (proto.bearerAtEnd == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
971                 if (!proto.isEmergency && (apnTypes & ApnSetting.TYPE_IMS) != 0) {
972                     updateHandoverState(proto, connectionState.getState());
973                 }
974                 if (proto.isEmergency && (apnTypes & ApnSetting.TYPE_EMERGENCY) != 0) {
975                     updateHandoverState(proto, connectionState.getState());
976                 }
977             }
978         }
979     }
980 
updateHandoverState(VoiceCallSession proto, int dataState)981     private void updateHandoverState(VoiceCallSession proto, int dataState) {
982         switch (dataState) {
983             case TelephonyManager.DATA_HANDOVER_IN_PROGRESS:
984                 proto.handoverInProgress = true;
985                 break;
986             default:
987                 // All other states are considered as not in handover
988                 proto.handoverInProgress = false;
989         }
990     }
991 }
992