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