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