• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.metrics;
18 
19 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
20 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
21 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
22 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
23 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
24 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND;
25 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
26 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
27 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
28 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND;
29 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST;
30 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW;
31 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST;
32 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL;
33 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW;
34 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST;
35 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW;
36 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
37 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST;
38 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW;
39 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT;
40 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
41 
42 import android.annotation.Nullable;
43 import android.content.Context;
44 import android.net.wifi.WifiInfo;
45 import android.net.wifi.WifiManager;
46 import android.os.SystemClock;
47 import android.telecom.VideoProfile;
48 import android.telecom.VideoProfile.VideoState;
49 import android.telephony.Annotation.NetworkType;
50 import android.telephony.DisconnectCause;
51 import android.telephony.ServiceState;
52 import android.telephony.TelephonyManager;
53 import android.telephony.ims.ImsReasonInfo;
54 import android.telephony.ims.ImsStreamMediaProfile;
55 import android.util.LongSparseArray;
56 import android.util.SparseArray;
57 import android.util.SparseIntArray;
58 
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.telephony.Call;
61 import com.android.internal.telephony.Connection;
62 import com.android.internal.telephony.DriverCall;
63 import com.android.internal.telephony.GsmCdmaConnection;
64 import com.android.internal.telephony.Phone;
65 import com.android.internal.telephony.PhoneConstants;
66 import com.android.internal.telephony.PhoneFactory;
67 import com.android.internal.telephony.ServiceStateTracker;
68 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
69 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
70 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
71 import com.android.internal.telephony.uicc.UiccController;
72 import com.android.telephony.Rlog;
73 
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.HashSet;
77 import java.util.List;
78 import java.util.Set;
79 import java.util.stream.Collectors;
80 
81 /** Collects voice call events per phone ID for the pulled atom. */
82 public class VoiceCallSessionStats {
83     private static final String TAG = VoiceCallSessionStats.class.getSimpleName();
84 
85     /** Upper bounds of each call setup duration category in milliseconds. */
86     private static final int CALL_SETUP_DURATION_UNKNOWN = 0;
87     private static final int CALL_SETUP_DURATION_EXTREMELY_FAST = 400;
88     private static final int CALL_SETUP_DURATION_ULTRA_FAST = 700;
89     private static final int CALL_SETUP_DURATION_VERY_FAST = 1000;
90     private static final int CALL_SETUP_DURATION_FAST = 1500;
91     private static final int CALL_SETUP_DURATION_NORMAL = 2500;
92     private static final int CALL_SETUP_DURATION_SLOW = 4000;
93     private static final int CALL_SETUP_DURATION_VERY_SLOW = 6000;
94     private static final int CALL_SETUP_DURATION_ULTRA_SLOW = 10000;
95     // CALL_SETUP_DURATION_EXTREMELY_SLOW has no upper bound (it includes everything above 10000)
96 
97     /** Number of buckets for codec quality, from UNKNOWN to FULLBAND. */
98     private static final int CODEC_QUALITY_COUNT = 5;
99 
100     /**
101      * Threshold to calculate the main audio codec quality of the call.
102      *
103      * The audio codec quality was equal to or greater than the main audio codec quality for
104      * at least 70% of the call.
105      */
106     private static final int MAIN_CODEC_QUALITY_THRESHOLD = 70;
107 
108     /** Holds the audio codec value for CS calls. */
109     private static final SparseIntArray CS_CODEC_MAP = buildGsmCdmaCodecMap();
110 
111     /** Holds the audio codec value for IMS calls. */
112     private static final SparseIntArray IMS_CODEC_MAP = buildImsCodecMap();
113 
114     /** Holds setup duration buckets with values as their upper bounds in milliseconds. */
115     private static final SparseIntArray CALL_SETUP_DURATION_MAP = buildCallSetupDurationMap();
116 
117     /**
118      * Tracks statistics for each call connection, indexed with ID returned by {@link
119      * #getConnectionId}.
120      */
121     private final SparseArray<VoiceCallSession> mCallProtos = new SparseArray<>();
122 
123     /**
124      * Tracks usage of codecs for each call. The outer array is used to map each connection id to
125      * the corresponding codec usage. The inner array is used to map timestamp (key) with the
126      * codec in use (value).
127      */
128     private final SparseArray<LongSparseArray<Integer>> mCodecUsage = new SparseArray<>();
129 
130     /**
131      * Tracks call RAT usage.
132      *
133      * <p>RAT usage is mainly tied to phones rather than calls, since each phone can have multiple
134      * concurrent calls, and we do not want to count the RAT duration multiple times.
135      */
136     private final VoiceCallRatTracker mRatUsage = new VoiceCallRatTracker();
137 
138     private final int mPhoneId;
139     private final Phone mPhone;
140 
141     private final PersistAtomsStorage mAtomsStorage =
142             PhoneFactory.getMetricsCollector().getAtomsStorage();
143     private final UiccController mUiccController = UiccController.getInstance();
144 
VoiceCallSessionStats(int phoneId, Phone phone)145     public VoiceCallSessionStats(int phoneId, Phone phone) {
146         mPhoneId = phoneId;
147         mPhone = phone;
148     }
149 
150     /* CS calls */
151 
152     /** Updates internal states when previous CS calls are accepted to track MT call setup time. */
onRilAcceptCall(List<Connection> connections)153     public synchronized void onRilAcceptCall(List<Connection> connections) {
154         for (Connection conn : connections) {
155             addCall(conn);
156         }
157     }
158 
159     /** Updates internal states when a CS MO call is created. */
onRilDial(Connection conn)160     public synchronized void onRilDial(Connection conn) {
161         addCall(conn);
162     }
163 
164     /**
165      * Updates internal states when CS calls are created or terminated, or CS call state is changed.
166      */
onRilCallListChanged(List<GsmCdmaConnection> connections)167     public synchronized void onRilCallListChanged(List<GsmCdmaConnection> connections) {
168         for (Connection conn : connections) {
169             int id = getConnectionId(conn);
170             if (!mCallProtos.contains(id)) {
171                 // handle new connections
172                 if (conn.getDisconnectCause() == DisconnectCause.NOT_DISCONNECTED) {
173                     addCall(conn);
174                     checkCallSetup(conn, mCallProtos.get(id));
175                 } else {
176                     logd("onRilCallListChanged: skip adding disconnected connection");
177                 }
178             } else {
179                 VoiceCallSession proto = mCallProtos.get(id);
180                 // handle call state change
181                 checkCallSetup(conn, proto);
182                 // handle terminated connections
183                 if (conn.getDisconnectCause() != DisconnectCause.NOT_DISCONNECTED) {
184                     proto.bearerAtEnd = getBearer(conn); // should be CS
185                     proto.disconnectReasonCode = conn.getDisconnectCause();
186                     proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
187                     proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
188                     finishCall(id);
189                 }
190             }
191         }
192         // NOTE: we cannot check stray connections (CS call in our list but not in RIL), as
193         // GsmCdmaCallTracker can call this with a partial list
194     }
195 
196     /* IMS calls */
197 
198     /** Updates internal states when an IMS MO call is created. */
onImsDial(ImsPhoneConnection conn)199     public synchronized void onImsDial(ImsPhoneConnection conn) {
200         addCall(conn);
201         if (conn.hasRttTextStream()) {
202             setRttStarted(conn);
203         }
204     }
205 
206     /** Updates internal states when an IMS MT call is created. */
onImsCallReceived(ImsPhoneConnection conn)207     public synchronized void onImsCallReceived(ImsPhoneConnection conn) {
208         addCall(conn);
209         if (conn.hasRttTextStream()) {
210             setRttStarted(conn);
211         }
212     }
213 
214     /** Updates internal states when previous IMS calls are accepted to track MT call setup time. */
onImsAcceptCall(List<Connection> connections)215     public synchronized void onImsAcceptCall(List<Connection> connections) {
216         for (Connection conn : connections) {
217             addCall(conn);
218         }
219     }
220 
221     /** Updates internal states when an IMS call is terminated. */
onImsCallTerminated( @ullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo)222     public synchronized void onImsCallTerminated(
223             @Nullable ImsPhoneConnection conn, ImsReasonInfo reasonInfo) {
224         if (conn == null) {
225             List<Integer> imsConnIds = getImsConnectionIds();
226             if (imsConnIds.size() == 1) {
227                 loge("onImsCallTerminated: ending IMS call w/ conn=null");
228                 finishImsCall(imsConnIds.get(0), reasonInfo);
229             } else {
230                 loge("onImsCallTerminated: %d IMS calls w/ conn=null", imsConnIds.size());
231             }
232         } else {
233             int id = getConnectionId(conn);
234             if (mCallProtos.contains(id)) {
235                 finishImsCall(id, reasonInfo);
236             } else {
237                 loge("onImsCallTerminated: untracked connection");
238                 // fake a call so at least some info can be tracked
239                 addCall(conn);
240                 finishImsCall(id, reasonInfo);
241             }
242         }
243     }
244 
245     /** Updates internal states when RTT is started on an IMS call. */
onRttStarted(ImsPhoneConnection conn)246     public synchronized void onRttStarted(ImsPhoneConnection conn) {
247         setRttStarted(conn);
248     }
249 
250     /* general & misc. */
251 
252     /** Updates internal states when audio codec for a call is changed. */
onAudioCodecChanged(Connection conn, int audioQuality)253     public synchronized void onAudioCodecChanged(Connection conn, int audioQuality) {
254         int id = getConnectionId(conn);
255         VoiceCallSession proto = mCallProtos.get(id);
256         if (proto == null) {
257             loge("onAudioCodecChanged: untracked connection");
258             return;
259         }
260         int codec = audioQualityToCodec(proto.bearerAtEnd, audioQuality);
261         proto.codecBitmask |= (1L << codec);
262 
263         if (mCodecUsage.contains(id)) {
264             mCodecUsage.get(id).append(getTimeMillis(), codec);
265         } else {
266             LongSparseArray<Integer> arr = new LongSparseArray<>();
267             arr.append(getTimeMillis(), codec);
268             mCodecUsage.put(id, arr);
269         }
270     }
271 
272     /** Updates internal states when video state changes. */
onVideoStateChange( ImsPhoneConnection conn, @VideoState int videoState)273     public synchronized void onVideoStateChange(
274             ImsPhoneConnection conn, @VideoState int videoState) {
275         int id = getConnectionId(conn);
276         VoiceCallSession proto = mCallProtos.get(id);
277         if (proto == null) {
278             loge("onVideoStateChange: untracked connection");
279             return;
280         }
281         logd("Video state = " + videoState);
282         if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
283             proto.videoEnabled = true;
284         }
285     }
286 
287     /** Updates internal states when multiparty state changes. */
onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty)288     public synchronized void onMultipartyChange(ImsPhoneConnection conn, boolean isMultiParty) {
289         int id = getConnectionId(conn);
290         VoiceCallSession proto = mCallProtos.get(id);
291         if (proto == null) {
292             loge("onMultipartyChange: untracked connection");
293             return;
294         }
295         logd("Multiparty = " + isMultiParty);
296         if (isMultiParty) {
297             proto.isMultiparty = true;
298         }
299     }
300 
301     /**
302      * Updates internal states when a call changes state to track setup time and status.
303      *
304      * <p>This is currently mainly used by IMS since CS call states are updated through {@link
305      * #onRilCallListChanged}.
306      */
onCallStateChanged(Call call)307     public synchronized void onCallStateChanged(Call call) {
308         for (Connection conn : call.getConnections()) {
309             VoiceCallSession proto = mCallProtos.get(getConnectionId(conn));
310             if (proto != null) {
311                 checkCallSetup(conn, proto);
312             } else {
313                 loge("onCallStateChanged: untracked connection");
314             }
315         }
316     }
317 
318     /** Updates internal states when an IMS call is handover to a CS call. */
onRilSrvccStateChanged(int state)319     public synchronized void onRilSrvccStateChanged(int state) {
320         List<Connection> handoverConnections = null;
321         if (mPhone.getImsPhone() != null) {
322             loge("onRilSrvccStateChanged: ImsPhone is null");
323         } else {
324             handoverConnections = mPhone.getImsPhone().getHandoverConnection();
325         }
326         List<Integer> imsConnIds;
327         if (handoverConnections == null) {
328             imsConnIds = getImsConnectionIds();
329             loge("onRilSrvccStateChanged: ImsPhone has no handover, we have %d", imsConnIds.size());
330         } else {
331             imsConnIds =
332                     handoverConnections.stream()
333                             .map(VoiceCallSessionStats::getConnectionId)
334                             .collect(Collectors.toList());
335         }
336         switch (state) {
337             case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
338                 // connection will now be CS
339                 for (int id : imsConnIds) {
340                     VoiceCallSession proto = mCallProtos.get(id);
341                     proto.srvccCompleted = true;
342                     proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
343                 }
344                 break;
345             case TelephonyManager.SRVCC_STATE_HANDOVER_FAILED:
346                 for (int id : imsConnIds) {
347                     mCallProtos.get(id).srvccFailureCount++;
348                 }
349                 break;
350             case TelephonyManager.SRVCC_STATE_HANDOVER_CANCELED:
351                 for (int id : imsConnIds) {
352                     mCallProtos.get(id).srvccCancellationCount++;
353                 }
354                 break;
355             default: // including STARTED and NONE, do nothing
356         }
357     }
358 
359     /** Updates internal states when RAT changes. */
onServiceStateChanged(ServiceState state)360     public synchronized void onServiceStateChanged(ServiceState state) {
361         if (hasCalls()) {
362             updateRatTracker(state);
363         }
364     }
365 
366     /* internal */
367 
368     /**
369      * Adds a call connection.
370      *
371      * <p>Should be called when the call is created, and when setup begins (upon {@code
372      * RilRequest.RIL_REQUEST_ANSWER} or {@code ImsCommand.IMS_CMD_ACCEPT}).
373      */
addCall(Connection conn)374     private void addCall(Connection conn) {
375         int id = getConnectionId(conn);
376         if (mCallProtos.contains(id)) {
377             // mostly handles ringing MT call getting accepted (MT call setup begins)
378             logd("addCall: resetting setup info");
379             VoiceCallSession proto = mCallProtos.get(id);
380             proto.setupBeginMillis = getTimeMillis();
381             proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
382         } else {
383             int bearer = getBearer(conn);
384             ServiceState serviceState = getServiceState();
385             @NetworkType int rat = getRat(serviceState);
386 
387             VoiceCallSession proto = new VoiceCallSession();
388 
389             proto.bearerAtStart = bearer;
390             proto.bearerAtEnd = bearer;
391             proto.direction = getDirection(conn);
392             proto.setupDuration = VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN;
393             proto.setupFailed = true;
394             proto.disconnectReasonCode = conn.getDisconnectCause();
395             proto.disconnectExtraCode = conn.getPreciseDisconnectCause();
396             proto.disconnectExtraMessage = conn.getVendorDisconnectCause();
397             proto.ratAtStart = rat;
398             proto.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
399             proto.ratAtEnd = rat;
400             proto.ratSwitchCount = 0L;
401             proto.codecBitmask = 0L;
402             proto.simSlotIndex = mPhoneId;
403             proto.isMultiSim = SimSlotState.isMultiSim();
404             proto.isEsim = SimSlotState.isEsim(mPhoneId);
405             proto.carrierId = mPhone.getCarrierId();
406             proto.srvccCompleted = false;
407             proto.srvccFailureCount = 0L;
408             proto.srvccCancellationCount = 0L;
409             proto.rttEnabled = false;
410             proto.isEmergency = conn.isEmergencyCall();
411             proto.isRoaming = serviceState != null ? serviceState.getVoiceRoaming() : false;
412             proto.isMultiparty = conn.isMultiparty();
413 
414             // internal fields for tracking
415             proto.setupBeginMillis = getTimeMillis();
416 
417             proto.concurrentCallCountAtStart = mCallProtos.size();
418             mCallProtos.put(id, proto);
419 
420             // RAT call count needs to be updated
421             updateRatTracker(serviceState);
422         }
423     }
424 
425     /** Sends the call metrics to persist storage when it is finished. */
finishCall(int connectionId)426     private void finishCall(int connectionId) {
427         VoiceCallSession proto = mCallProtos.get(connectionId);
428         if (proto == null) {
429             loge("finishCall: could not find call to be removed");
430             return;
431         }
432         mCallProtos.delete(connectionId);
433         proto.concurrentCallCountAtEnd = mCallProtos.size();
434 
435         // Calculate signal strength at the end of the call
436         proto.signalStrengthAtEnd = getSignalStrength(proto.ratAtEnd);
437 
438         // Calculate main codec quality
439         proto.mainCodecQuality = finalizeMainCodecQuality(connectionId);
440 
441         // ensure internal fields are cleared
442         proto.setupBeginMillis = 0L;
443 
444         // sanitize for javanano & StatsEvent
445         if (proto.disconnectExtraMessage == null) {
446             proto.disconnectExtraMessage = "";
447         }
448 
449         // Retry populating carrier ID if it was invalid
450         if (proto.carrierId <= 0) {
451             proto.carrierId = mPhone.getCarrierId();
452         }
453 
454         mAtomsStorage.addVoiceCallSession(proto);
455 
456         // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls)
457         if (!hasCalls()) {
458             mRatUsage.conclude(getTimeMillis());
459             mAtomsStorage.addVoiceCallRatUsage(mRatUsage);
460             mRatUsage.clear();
461         }
462     }
463 
setRttStarted(ImsPhoneConnection conn)464     private void setRttStarted(ImsPhoneConnection conn) {
465         VoiceCallSession proto = mCallProtos.get(getConnectionId(conn));
466         if (proto == null) {
467             loge("onRttStarted: untracked connection");
468             return;
469         }
470         // should be IMS w/o SRVCC
471         if (proto.bearerAtStart != getBearer(conn) || proto.bearerAtEnd != getBearer(conn)) {
472             loge("onRttStarted: connection bearer mismatch but proceeding");
473         }
474         proto.rttEnabled = true;
475     }
476 
477     /** Returns a {@link Set} of Connection IDs so RAT usage can be correctly tracked. */
getConnectionIds()478     private Set<Integer> getConnectionIds() {
479         Set<Integer> ids = new HashSet<>();
480         for (int i = 0; i < mCallProtos.size(); i++) {
481             ids.add(mCallProtos.keyAt(i));
482         }
483         return ids;
484     }
485 
getImsConnectionIds()486     private List<Integer> getImsConnectionIds() {
487         List<Integer> imsConnIds = new ArrayList<>(mCallProtos.size());
488         for (int i = 0; i < mCallProtos.size(); i++) {
489             if (mCallProtos.valueAt(i).bearerAtEnd
490                     == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
491                 imsConnIds.add(mCallProtos.keyAt(i));
492             }
493         }
494         return imsConnIds;
495     }
496 
hasCalls()497     private boolean hasCalls() {
498         return mCallProtos.size() > 0;
499     }
500 
checkCallSetup(Connection conn, VoiceCallSession proto)501     private void checkCallSetup(Connection conn, VoiceCallSession proto) {
502         if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) {
503             proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis);
504             proto.setupDuration = classifySetupDuration(proto.setupDurationMillis);
505             proto.setupBeginMillis = 0L;
506         }
507         // Clear setupFailed if call now active, but otherwise leave it unchanged
508         // This block is executed only once, when call becomes active for the first time.
509         if (proto.setupFailed && conn.getState() == Call.State.ACTIVE) {
510             proto.setupFailed = false;
511             // Track RAT when voice call is connected.
512             ServiceState serviceState = getServiceState();
513             proto.ratAtConnected = getRat(serviceState);
514             // Reset list of codecs with the last codec at the present time. In this way, we
515             // track codec quality only after call is connected and not while ringing.
516             resetCodecList(conn);
517         }
518     }
519 
updateRatTracker(ServiceState state)520     private void updateRatTracker(ServiceState state) {
521         @NetworkType int rat = getRat(state);
522         int band = ServiceStateStats.getBand(state, rat);
523 
524         mRatUsage.add(mPhone.getCarrierId(), rat, getTimeMillis(), getConnectionIds());
525         for (int i = 0; i < mCallProtos.size(); i++) {
526             VoiceCallSession proto = mCallProtos.valueAt(i);
527             if (proto.ratAtEnd != rat) {
528                 proto.ratSwitchCount++;
529                 proto.ratAtEnd = rat;
530             }
531             proto.bandAtEnd = band;
532             // assuming that SIM carrier ID does not change during the call
533         }
534     }
535 
finishImsCall(int id, ImsReasonInfo reasonInfo)536     private void finishImsCall(int id, ImsReasonInfo reasonInfo) {
537         VoiceCallSession proto = mCallProtos.get(id);
538         proto.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
539         proto.disconnectReasonCode = reasonInfo.mCode;
540         proto.disconnectExtraCode = reasonInfo.mExtraCode;
541         proto.disconnectExtraMessage = ImsStats.filterExtraMessage(reasonInfo.mExtraMessage);
542         finishCall(id);
543     }
544 
getServiceState()545     private @Nullable ServiceState getServiceState() {
546         ServiceStateTracker tracker = mPhone.getServiceStateTracker();
547         return tracker != null ? tracker.getServiceState() : null;
548     }
549 
getDirection(Connection conn)550     private static int getDirection(Connection conn) {
551         return conn.isIncoming()
552                 ? VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT
553                 : VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
554     }
555 
getBearer(Connection conn)556     private static int getBearer(Connection conn) {
557         int phoneType = conn.getPhoneType();
558         switch (phoneType) {
559             case PhoneConstants.PHONE_TYPE_GSM:
560             case PhoneConstants.PHONE_TYPE_CDMA:
561                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
562             case PhoneConstants.PHONE_TYPE_IMS:
563                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
564             default:
565                 loge("getBearer: unknown phoneType=%d", phoneType);
566                 return VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
567         }
568     }
569 
getRat(@ullable ServiceState state)570     private @NetworkType int getRat(@Nullable ServiceState state) {
571         if (state == null) {
572             return TelephonyManager.NETWORK_TYPE_UNKNOWN;
573         }
574         boolean isWifiCall =
575                 mPhone.getImsPhone() != null
576                         && mPhone.getImsPhone().isWifiCallingEnabled()
577                         && state.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
578         return isWifiCall ? TelephonyManager.NETWORK_TYPE_IWLAN : state.getVoiceNetworkType();
579     }
580 
581     /** Returns the signal strength. */
getSignalStrength(@etworkType int rat)582     private int getSignalStrength(@NetworkType int rat) {
583         if (rat == TelephonyManager.NETWORK_TYPE_IWLAN) {
584             return getSignalStrengthWifi();
585         } else {
586             return getSignalStrengthCellular();
587         }
588     }
589 
590     /** Returns the signal strength of WiFi. */
getSignalStrengthWifi()591     private int getSignalStrengthWifi() {
592         WifiManager wifiManager =
593                 (WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE);
594         WifiInfo wifiInfo = wifiManager.getConnectionInfo();
595         int result = VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
596         if (wifiInfo != null) {
597             int level = wifiManager.calculateSignalLevel(wifiInfo.getRssi());
598             int max = wifiManager.getMaxSignalLevel();
599             // Scale result into 0 to 4 range.
600             result = VOICE_CALL_SESSION__SIGNAL_STRENGTH_AT_END__SIGNAL_STRENGTH_GREAT
601                     * level / max;
602             logd("WiFi level: " + result + " (" + level + "/" + max + ")");
603         }
604         return result;
605     }
606 
607     /** Returns the signal strength of cellular RAT. */
getSignalStrengthCellular()608     private int getSignalStrengthCellular() {
609         return mPhone.getSignalStrength().getLevel();
610     }
611 
612     /** Resets the list of codecs used for the connection with only the codec currently in use. */
resetCodecList(Connection conn)613     private void resetCodecList(Connection conn) {
614         int id = getConnectionId(conn);
615         LongSparseArray<Integer> codecUsage = mCodecUsage.get(id);
616         if (codecUsage != null) {
617             int lastCodec = codecUsage.valueAt(codecUsage.size() - 1);
618             LongSparseArray<Integer> arr = new LongSparseArray<>();
619             arr.append(getTimeMillis(), lastCodec);
620             mCodecUsage.put(id, arr);
621         }
622     }
623 
624     /** Returns the main codec quality used during the call. */
finalizeMainCodecQuality(int connectionId)625     private int finalizeMainCodecQuality(int connectionId) {
626         // Retrieve information about codec usage for this call and remove it from main array.
627         if (!mCodecUsage.contains(connectionId)) {
628             return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
629         }
630         LongSparseArray<Integer> codecUsage = mCodecUsage.get(connectionId);
631         mCodecUsage.delete(connectionId);
632 
633         // Append fake entry at the end, to facilitate the calculation of time for each codec.
634         codecUsage.put(getTimeMillis(), AudioCodec.AUDIO_CODEC_UNKNOWN);
635 
636         // Calculate array with time for each quality
637         int totalTime = 0;
638         long[] timePerQuality = new long[CODEC_QUALITY_COUNT];
639         for (int i = 0; i < codecUsage.size() - 1; i++) {
640             long time = codecUsage.keyAt(i + 1) - codecUsage.keyAt(i);
641             int quality = getCodecQuality(codecUsage.valueAt(i));
642             timePerQuality[quality] += time;
643             totalTime += time;
644         }
645         logd("Time per codec quality = " + Arrays.toString(timePerQuality));
646 
647         // We calculate 70% duration of the call as the threshold for the main audio codec quality
648         // and iterate on all codec qualities. As soon as the sum of codec duration is greater than
649         // the threshold, we have identified the main codec quality.
650         long timeAtMinimumQuality = 0;
651         long timeThreshold = totalTime * MAIN_CODEC_QUALITY_THRESHOLD / 100;
652         for (int i = CODEC_QUALITY_COUNT - 1; i >= 0; i--) {
653             timeAtMinimumQuality += timePerQuality[i];
654             if (timeAtMinimumQuality >= timeThreshold) {
655                 return i;
656             }
657         }
658         return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
659     }
660 
getCodecQuality(int codec)661     private int getCodecQuality(int codec) {
662         switch(codec) {
663             case AudioCodec.AUDIO_CODEC_AMR:
664             case AudioCodec.AUDIO_CODEC_QCELP13K:
665             case AudioCodec.AUDIO_CODEC_EVRC:
666             case AudioCodec.AUDIO_CODEC_EVRC_B:
667             case AudioCodec.AUDIO_CODEC_EVRC_NW:
668             case AudioCodec.AUDIO_CODEC_GSM_EFR:
669             case AudioCodec.AUDIO_CODEC_GSM_FR:
670             case AudioCodec.AUDIO_CODEC_GSM_HR:
671             case AudioCodec.AUDIO_CODEC_G711U:
672             case AudioCodec.AUDIO_CODEC_G723:
673             case AudioCodec.AUDIO_CODEC_G711A:
674             case AudioCodec.AUDIO_CODEC_G722:
675             case AudioCodec.AUDIO_CODEC_G711AB:
676             case AudioCodec.AUDIO_CODEC_G729:
677             case AudioCodec.AUDIO_CODEC_EVS_NB:
678                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
679             case AudioCodec.AUDIO_CODEC_AMR_WB:
680             case AudioCodec.AUDIO_CODEC_EVS_WB:
681             case AudioCodec.AUDIO_CODEC_EVRC_WB:
682                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_WIDEBAND;
683             case AudioCodec.AUDIO_CODEC_EVS_SWB:
684                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
685             case AudioCodec.AUDIO_CODEC_EVS_FB:
686                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND;
687             default:
688                 return VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
689         }
690     }
691 
isSetupFinished(@ullable Call call)692     private static boolean isSetupFinished(@Nullable Call call) {
693         // NOTE: when setup is finished for MO calls, it is not successful yet.
694         if (call != null) {
695             switch (call.getState()) {
696                 case ACTIVE: // MT setup: accepted to ACTIVE
697                 case ALERTING: // MO setup: dial to ALERTING
698                     return true;
699                 default: // do nothing
700             }
701         }
702         return false;
703     }
704 
audioQualityToCodec(int bearer, int audioQuality)705     private static int audioQualityToCodec(int bearer, int audioQuality) {
706         switch (bearer) {
707             case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS:
708                 return CS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN);
709             case VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS:
710                 return IMS_CODEC_MAP.get(audioQuality, AudioCodec.AUDIO_CODEC_UNKNOWN);
711             default:
712                 loge("audioQualityToCodec: unknown bearer %d", bearer);
713                 return AudioCodec.AUDIO_CODEC_UNKNOWN;
714         }
715     }
716 
classifySetupDuration(int durationMillis)717     private static int classifySetupDuration(int durationMillis) {
718         // keys in CALL_SETUP_DURATION_MAP are upper bounds in ascending order
719         for (int i = 0; i < CALL_SETUP_DURATION_MAP.size(); i++) {
720             if (durationMillis < CALL_SETUP_DURATION_MAP.keyAt(i)) {
721                 return CALL_SETUP_DURATION_MAP.valueAt(i);
722             }
723         }
724         return VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_SLOW;
725     }
726 
727     /**
728      * Generates an ID for each connection, which should be the same for IMS and CS connections
729      * involved in the same SRVCC.
730      *
731      * <p>Among the fields copied from ImsPhoneConnection to GsmCdmaConnection during SRVCC, the
732      * Connection's create time seems to be the best choice for ID (assuming no multiple calls in a
733      * millisecond). The 64-bit time is truncated to 32-bit so it can be used as an index in various
734      * data structures, which is good for calls shorter than 49 days.
735      */
getConnectionId(Connection conn)736     private static int getConnectionId(Connection conn) {
737         return conn == null ? 0 : (int) conn.getCreateTime();
738     }
739 
740     @VisibleForTesting
getTimeMillis()741     protected long getTimeMillis() {
742         return SystemClock.elapsedRealtime();
743     }
744 
logd(String format, Object... args)745     private static void logd(String format, Object... args) {
746         Rlog.d(TAG, String.format(format, args));
747     }
748 
loge(String format, Object... args)749     private static void loge(String format, Object... args) {
750         Rlog.e(TAG, String.format(format, args));
751     }
752 
buildGsmCdmaCodecMap()753     private static SparseIntArray buildGsmCdmaCodecMap() {
754         SparseIntArray map = new SparseIntArray();
755         map.put(DriverCall.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR);
756         map.put(DriverCall.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB);
757         map.put(DriverCall.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR);
758         map.put(DriverCall.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR);
759         map.put(DriverCall.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR);
760         map.put(DriverCall.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC);
761         map.put(DriverCall.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B);
762         map.put(DriverCall.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB);
763         map.put(DriverCall.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW);
764         return map;
765     }
766 
buildImsCodecMap()767     private static SparseIntArray buildImsCodecMap() {
768         SparseIntArray map = new SparseIntArray();
769         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR, AudioCodec.AUDIO_CODEC_AMR);
770         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB, AudioCodec.AUDIO_CODEC_AMR_WB);
771         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K, AudioCodec.AUDIO_CODEC_QCELP13K);
772         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC, AudioCodec.AUDIO_CODEC_EVRC);
773         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B, AudioCodec.AUDIO_CODEC_EVRC_B);
774         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB, AudioCodec.AUDIO_CODEC_EVRC_WB);
775         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW, AudioCodec.AUDIO_CODEC_EVRC_NW);
776         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR, AudioCodec.AUDIO_CODEC_GSM_EFR);
777         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR, AudioCodec.AUDIO_CODEC_GSM_FR);
778         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR, AudioCodec.AUDIO_CODEC_GSM_HR);
779         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711U, AudioCodec.AUDIO_CODEC_G711U);
780         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G723, AudioCodec.AUDIO_CODEC_G723);
781         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711A, AudioCodec.AUDIO_CODEC_G711A);
782         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G722, AudioCodec.AUDIO_CODEC_G722);
783         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G711AB, AudioCodec.AUDIO_CODEC_G711AB);
784         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_G729, AudioCodec.AUDIO_CODEC_G729);
785         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB, AudioCodec.AUDIO_CODEC_EVS_NB);
786         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB, AudioCodec.AUDIO_CODEC_EVS_WB);
787         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB, AudioCodec.AUDIO_CODEC_EVS_SWB);
788         map.put(ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB, AudioCodec.AUDIO_CODEC_EVS_FB);
789         return map;
790     }
791 
buildCallSetupDurationMap()792     private static SparseIntArray buildCallSetupDurationMap() {
793         SparseIntArray map = new SparseIntArray();
794 
795         map.put(
796                 CALL_SETUP_DURATION_UNKNOWN,
797                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_UNKNOWN);
798         map.put(
799                 CALL_SETUP_DURATION_EXTREMELY_FAST,
800                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_EXTREMELY_FAST);
801         map.put(
802                 CALL_SETUP_DURATION_ULTRA_FAST,
803                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_FAST);
804         map.put(
805                 CALL_SETUP_DURATION_VERY_FAST,
806                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_FAST);
807         map.put(
808                 CALL_SETUP_DURATION_FAST,
809                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_FAST);
810         map.put(
811                 CALL_SETUP_DURATION_NORMAL,
812                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_NORMAL);
813         map.put(
814                 CALL_SETUP_DURATION_SLOW,
815                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_SLOW);
816         map.put(
817                 CALL_SETUP_DURATION_VERY_SLOW,
818                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_VERY_SLOW);
819         map.put(
820                 CALL_SETUP_DURATION_ULTRA_SLOW,
821                 VOICE_CALL_SESSION__SETUP_DURATION__CALL_SETUP_DURATION_ULTRA_SLOW);
822         // anything above would be CALL_SETUP_DURATION_EXTREMELY_SLOW
823 
824         return map;
825     }
826 }
827