1 /*
2  * Copyright (C) 2015 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.server.telecom;
18 
19 import android.content.Context;
20 import android.os.SystemProperties;
21 
22 import android.telecom.Connection;
23 import android.telecom.DisconnectCause;
24 import android.telecom.Logging.EventManager;
25 import android.telecom.ParcelableCallAnalytics;
26 import android.telecom.TelecomAnalytics;
27 import android.telecom.TelecomManager;
28 import android.telephony.SubscriptionInfo;
29 import android.telephony.SubscriptionManager;
30 import android.util.Base64;
31 import android.telecom.Log;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.IndentingPrintWriter;
35 import com.android.server.telecom.nano.TelecomLogClass;
36 
37 import java.io.PrintWriter;
38 import java.time.Instant;
39 import java.time.ZoneOffset;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.HashMap;
45 import java.util.LinkedList;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.concurrent.LinkedBlockingDeque;
49 import java.util.stream.Collectors;
50 
51 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
52 import static android.telecom.TelecomAnalytics.SessionTiming;
53 
54 /**
55  * A class that collects and stores data on how calls are being made, in order to
56  * aggregate these into useful statistics.
57  */
58 public class Analytics {
59     public static final String ANALYTICS_DUMPSYS_ARG = "analytics";
60     private static final String CLEAR_ANALYTICS_ARG = "clear";
61 
62     public static final Map<String, Integer> sLogEventToAnalyticsEvent =
63             new HashMap<String, Integer>() {{
64                 put(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
65                         AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT);
66                 put(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD);
67                 put(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD);
68                 put(LogUtils.Events.SWAP, AnalyticsEvent.SWAP);
69                 put(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING);
70                 put(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH);
71                 put(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE);
72                 put(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT);
73                 put(LogUtils.Events.MUTE, AnalyticsEvent.MUTE);
74                 put(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE);
75                 put(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT);
76                 put(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE);
77                 put(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET);
78                 put(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER);
79                 put(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE);
80                 put(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED);
81                 put(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED);
82                 put(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED);
83                 put(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD);
84                 put(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD);
85                 put(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL);
86                 put(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT);
87                 put(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT);
88                 put(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE);
89                 put(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED);
90                 put(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD);
91                 put(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING);
92                 put(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION);
93                 put(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS);
94                 put(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND);
95                 put(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT);
96                 put(LogUtils.Events.DIRECT_TO_VM_INITIATED, AnalyticsEvent.DIRECT_TO_VM_INITIATED);
97                 put(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED);
98                 put(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED);
99                 put(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED);
100                 put(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT);
101             }};
102 
103     public static final Map<String, Integer> sLogSessionToSessionId =
104             new HashMap<String, Integer> () {{
105                 put(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL);
106                 put(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL);
107                 put(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL);
108                 put(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL);
109                 put(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL);
110                 put(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE);
111                 put(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE);
112                 put(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE);
113                 put(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
114                         SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
115                 put(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE);
116                 put(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING);
117                 put(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING);
118                 put(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED);
119                 put(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD);
120                 put(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL);
121                 put(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED);
122                 put(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
123                         SessionTiming.CSW_ADD_CONFERENCE_CALL);
124 
125             }};
126 
127     public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming =
128             new HashMap<String, Integer>() {{
129                 put(LogUtils.Events.Timings.ACCEPT_TIMING,
130                         ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING);
131                 put(LogUtils.Events.Timings.REJECT_TIMING,
132                         ParcelableCallAnalytics.EventTiming.REJECT_TIMING);
133                 put(LogUtils.Events.Timings.DISCONNECT_TIMING,
134                         ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING);
135                 put(LogUtils.Events.Timings.HOLD_TIMING,
136                         ParcelableCallAnalytics.EventTiming.HOLD_TIMING);
137                 put(LogUtils.Events.Timings.UNHOLD_TIMING,
138                         ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING);
139                 put(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
140                         ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING);
141                 put(LogUtils.Events.Timings.BIND_CS_TIMING,
142                         ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING);
143                 put(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
144                         ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING);
145                 put(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
146                         ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING);
147                 put(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
148                         ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING);
149                 put(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
150                         ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
151                 put(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
152                         ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
153                 put(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
154                         ParcelableCallAnalytics.EventTiming.
155                                 START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING);
156             }};
157 
158     public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
159     static {
160         for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
e.getValue()161             sSessionIdToLogSession.put(e.getValue(), e.getKey());
162         }
163     }
164 
165     public static class CallInfo {
setCallStartTime(long startTime)166         public void setCallStartTime(long startTime) {
167         }
168 
setCallEndTime(long endTime)169         public void setCallEndTime(long endTime) {
170         }
171 
setCallIsAdditional(boolean isAdditional)172         public void setCallIsAdditional(boolean isAdditional) {
173         }
174 
setCallIsEmergency(boolean isEmergency)175         public void setCallIsEmergency(boolean isEmergency) {
176         }
177 
setCallIsInterrupted(boolean isInterrupted)178         public void setCallIsInterrupted(boolean isInterrupted) {
179         }
180 
setCallDisconnectCause(DisconnectCause disconnectCause)181         public void setCallDisconnectCause(DisconnectCause disconnectCause) {
182         }
183 
addCallTechnology(int callTechnology)184         public void addCallTechnology(int callTechnology) {
185         }
186 
setCreatedFromExistingConnection(boolean createdFromExistingConnection)187         public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
188         }
189 
setCallConnectionService(String connectionServiceName)190         public void setCallConnectionService(String connectionServiceName) {
191         }
192 
setCallEvents(EventManager.EventRecord records)193         public void setCallEvents(EventManager.EventRecord records) {
194         }
195 
setCallIsVideo(boolean isVideo)196         public void setCallIsVideo(boolean isVideo) {
197         }
198 
addVideoEvent(int eventId, int videoState)199         public void addVideoEvent(int eventId, int videoState) {
200         }
201 
addInCallService(String serviceName, int type, long boundDuration, boolean isNullBinding)202         public void addInCallService(String serviceName, int type, long boundDuration,
203                 boolean isNullBinding) {
204         }
205 
addCallProperties(int properties)206         public void addCallProperties(int properties) {
207         }
208 
setCallSource(int callSource)209         public void setCallSource(int callSource) {
210         }
211     }
212 
213     /**
214      * A class that holds data associated with a call.
215      */
216     @VisibleForTesting
217     public static class CallInfoImpl extends CallInfo {
218         public String callId;
219         public long startTime;  // start time in milliseconds since the epoch. 0 if not yet set.
220         public long endTime;  // end time in milliseconds since the epoch. 0 if not yet set.
221         public int callDirection;  // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
222                                    // or OUTGOING_DIRECTION.
223         public boolean isAdditionalCall = false;  // true if the call came in while another call was
224                                                   // in progress or if the user dialed this call
225                                                   // while in the middle of another call.
226         public boolean isInterrupted = false;  // true if the call was interrupted by an incoming
227                                                // or outgoing call.
228         public int callTechnologies;  // bitmask denoting which technologies a call used.
229 
230         // true if the Telecom Call object was created from an existing connection via
231         // CallsManager#createCallForExistingConnection, for example, by ImsConference.
232         public boolean createdFromExistingConnection = false;
233 
234         public DisconnectCause callTerminationReason;
235         public String connectionService;
236         public boolean isEmergency = false;
237 
238         public EventManager.EventRecord callEvents;
239 
240         public boolean isVideo = false;
241         public List<TelecomLogClass.VideoEvent> videoEvents;
242         public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
243         public int callProperties = 0;
244         public int callSource = CALL_SOURCE_UNSPECIFIED;
245 
246         private long mTimeOfLastVideoEvent = -1;
247 
CallInfoImpl(String callId, int callDirection)248         CallInfoImpl(String callId, int callDirection) {
249             this.callId = callId;
250             startTime = 0;
251             endTime = 0;
252             this.callDirection = callDirection;
253             callTechnologies = 0;
254             connectionService = "";
255             videoEvents = new LinkedList<>();
256             inCallServiceInfos = new LinkedList<>();
257         }
258 
CallInfoImpl(CallInfoImpl other)259         CallInfoImpl(CallInfoImpl other) {
260             this.callId = other.callId;
261             this.startTime = other.startTime;
262             this.endTime = other.endTime;
263             this.callDirection = other.callDirection;
264             this.isAdditionalCall = other.isAdditionalCall;
265             this.isInterrupted = other.isInterrupted;
266             this.callTechnologies = other.callTechnologies;
267             this.createdFromExistingConnection = other.createdFromExistingConnection;
268             this.connectionService = other.connectionService;
269             this.isEmergency = other.isEmergency;
270             this.callEvents = other.callEvents;
271             this.isVideo = other.isVideo;
272             this.videoEvents = other.videoEvents;
273             this.callProperties = other.callProperties;
274             this.callSource = other.callSource;
275 
276             if (other.callTerminationReason != null) {
277                 this.callTerminationReason = new DisconnectCause(
278                         other.callTerminationReason.getCode(),
279                         other.callTerminationReason.getLabel(),
280                         other.callTerminationReason.getDescription(),
281                         other.callTerminationReason.getReason(),
282                         other.callTerminationReason.getTone());
283             } else {
284                 this.callTerminationReason = null;
285             }
286         }
287 
288         @Override
setCallStartTime(long startTime)289         public void setCallStartTime(long startTime) {
290             Log.d(TAG, "setting startTime for call " + callId + " to " + startTime);
291             this.startTime = startTime;
292         }
293 
294         @Override
setCallEndTime(long endTime)295         public void setCallEndTime(long endTime) {
296             Log.d(TAG, "setting endTime for call " + callId + " to " + endTime);
297             this.endTime = endTime;
298         }
299 
300         @Override
setCallIsAdditional(boolean isAdditional)301         public void setCallIsAdditional(boolean isAdditional) {
302             Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional);
303             this.isAdditionalCall = isAdditional;
304         }
305 
306         @Override
setCallIsInterrupted(boolean isInterrupted)307         public void setCallIsInterrupted(boolean isInterrupted) {
308             Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted);
309             this.isInterrupted = isInterrupted;
310         }
311 
312         @Override
addCallTechnology(int callTechnology)313         public void addCallTechnology(int callTechnology) {
314             Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology);
315             this.callTechnologies |= callTechnology;
316         }
317 
318         @Override
setCallIsEmergency(boolean isEmergency)319         public void setCallIsEmergency(boolean isEmergency) {
320             Log.d(TAG, "setting call as emergency: " + isEmergency);
321             this.isEmergency = isEmergency;
322         }
323 
324         @Override
setCallDisconnectCause(DisconnectCause disconnectCause)325         public void setCallDisconnectCause(DisconnectCause disconnectCause) {
326             Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
327             this.callTerminationReason = disconnectCause;
328         }
329 
330         @Override
setCreatedFromExistingConnection(boolean createdFromExistingConnection)331         public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
332             Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to "
333                     + createdFromExistingConnection);
334             this.createdFromExistingConnection = createdFromExistingConnection;
335         }
336 
337         @Override
setCallConnectionService(String connectionServiceName)338         public void setCallConnectionService(String connectionServiceName) {
339             Log.d(TAG, "setting connection service for call " + callId + ": "
340                     + connectionServiceName);
341             this.connectionService = connectionServiceName;
342         }
343 
344         @Override
setCallEvents(EventManager.EventRecord records)345         public void setCallEvents(EventManager.EventRecord records) {
346             this.callEvents = records;
347         }
348 
349         @Override
setCallIsVideo(boolean isVideo)350         public void setCallIsVideo(boolean isVideo) {
351             this.isVideo = isVideo;
352         }
353 
354         @Override
addVideoEvent(int eventId, int videoState)355         public void addVideoEvent(int eventId, int videoState) {
356             long timeSinceLastEvent;
357             long currentTime = System.currentTimeMillis();
358             if (mTimeOfLastVideoEvent < 0) {
359                 timeSinceLastEvent = -1;
360             } else {
361                 timeSinceLastEvent = roundToOneSigFig(currentTime - mTimeOfLastVideoEvent);
362             }
363             mTimeOfLastVideoEvent = currentTime;
364 
365             videoEvents.add(new TelecomLogClass.VideoEvent()
366                     .setEventName(eventId)
367                     .setTimeSinceLastEventMillis(timeSinceLastEvent)
368                     .setVideoState(videoState));
369         }
370 
371         @Override
addInCallService(String serviceName, int type, long boundDuration, boolean isNullBinding)372         public void addInCallService(String serviceName, int type, long boundDuration,
373                 boolean isNullBinding) {
374             inCallServiceInfos.add(new TelecomLogClass.InCallServiceInfo()
375                     .setInCallServiceName(serviceName)
376                     .setInCallServiceType(type)
377                     .setBoundDurationMillis(boundDuration)
378                     .setIsNullBinding(isNullBinding));
379         }
380 
381         @Override
addCallProperties(int properties)382         public void addCallProperties(int properties) {
383             this.callProperties |= properties;
384         }
385 
386         @Override
setCallSource(int callSource)387         public void setCallSource(int callSource) {
388             this.callSource = callSource;
389         }
390 
391         @Override
toString()392         public String toString() {
393             return "{\n"
394                     + "    startTime: " + startTime + '\n'
395                     + "    endTime: " + endTime + '\n'
396                     + "    direction: " + getCallDirectionString() + '\n'
397                     + "    isAdditionalCall: " + isAdditionalCall + '\n'
398                     + "    isInterrupted: " + isInterrupted + '\n'
399                     + "    isEmergency: " + isEmergency + '\n'
400                     + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
401                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
402                     + "    connectionService: " + connectionService + '\n'
403                     + "    isVideoCall: " + isVideo + '\n'
404                     + "    inCallServices: " + getInCallServicesString() + '\n'
405                     + "    callProperties: " + Connection.propertiesToStringShort(callProperties)
406                     + '\n'
407                     + "    callSource: " + getCallSourceString() + '\n'
408                     + "}\n";
409         }
410 
toParcelableAnalytics()411         public ParcelableCallAnalytics toParcelableAnalytics() {
412             TelecomLogClass.CallLog analyticsProto = toProto();
413             List<ParcelableCallAnalytics.AnalyticsEvent> events =
414                     Arrays.stream(analyticsProto.callEvents)
415                     .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
416                                 callEventProto.getEventName(),
417                                 callEventProto.getTimeSinceLastEventMillis())
418                     ).collect(Collectors.toList());
419 
420             List<ParcelableCallAnalytics.EventTiming> timings =
421                     Arrays.stream(analyticsProto.callTimings)
422                     .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
423                             callTimingProto.getTimingName(),
424                             callTimingProto.getTimeMillis())
425                     ).collect(Collectors.toList());
426 
427             ParcelableCallAnalytics result = new ParcelableCallAnalytics(
428                     // rounds down to nearest 5 minute mark
429                     analyticsProto.getStartTime5Min(),
430                     analyticsProto.getCallDurationMillis(),
431                     analyticsProto.getType(),
432                     analyticsProto.getIsAdditionalCall(),
433                     analyticsProto.getIsInterrupted(),
434                     analyticsProto.getCallTechnologies(),
435                     analyticsProto.getCallTerminationCode(),
436                     analyticsProto.getIsEmergencyCall(),
437                     analyticsProto.connectionService[0],
438                     analyticsProto.getIsCreatedFromExistingConnection(),
439                     events,
440                     timings);
441 
442             result.setIsVideoCall(analyticsProto.getIsVideoCall());
443             result.setVideoEvents(Arrays.stream(analyticsProto.videoEvents)
444                     .map(videoEventProto -> new ParcelableCallAnalytics.VideoEvent(
445                             videoEventProto.getEventName(),
446                             videoEventProto.getTimeSinceLastEventMillis(),
447                             videoEventProto.getVideoState())
448                     ).collect(Collectors.toList()));
449 
450             result.setCallSource(analyticsProto.getCallSource());
451 
452             return result;
453         }
454 
toProto()455         public TelecomLogClass.CallLog toProto() {
456             TelecomLogClass.CallLog result = new TelecomLogClass.CallLog();
457             result.setStartTime5Min(
458                     startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
459 
460             // Rounds up to the nearest second.
461             long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime;
462             callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ?
463                     0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND);
464             result.setCallDurationMillis(callDuration);
465 
466             result.setType(callDirection)
467                     .setIsAdditionalCall(isAdditionalCall)
468                     .setIsInterrupted(isInterrupted)
469                     .setCallTechnologies(callTechnologies)
470                     .setCallTerminationCode(
471                             callTerminationReason == null ?
472                                     ParcelableCallAnalytics.STILL_CONNECTED :
473                                     callTerminationReason.getCode())
474                     .setIsEmergencyCall(isEmergency)
475                     .setIsCreatedFromExistingConnection(createdFromExistingConnection)
476                     .setIsEmergencyCall(isEmergency)
477                     .setIsVideoCall(isVideo)
478                     .setConnectionProperties(callProperties)
479                     .setCallSource(callSource);
480 
481             result.connectionService = new String[] {connectionService};
482             if (callEvents != null) {
483                 result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents());
484                 result.callTimings = callEvents.extractEventTimings().stream()
485                         .map(Analytics::logEventTimingToProtoEventTiming)
486                         .toArray(TelecomLogClass.EventTimingEntry[]::new);
487             }
488             result.videoEvents =
489                     videoEvents.toArray(new TelecomLogClass.VideoEvent[videoEvents.size()]);
490             result.inCallServices = inCallServiceInfos.toArray(
491                     new TelecomLogClass.InCallServiceInfo[inCallServiceInfos.size()]);
492 
493             return result;
494         }
495 
getCallDirectionString()496         private String getCallDirectionString() {
497             switch (callDirection) {
498                 case UNKNOWN_DIRECTION:
499                     return "UNKNOWN";
500                 case INCOMING_DIRECTION:
501                     return "INCOMING";
502                 case OUTGOING_DIRECTION:
503                     return "OUTGOING";
504                 default:
505                     return "UNKNOWN";
506             }
507         }
508 
getCallTechnologiesAsString()509         private String getCallTechnologiesAsString() {
510             StringBuilder s = new StringBuilder();
511             s.append('[');
512             if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA ");
513             if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM ");
514             if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP ");
515             if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS ");
516             if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY ");
517             s.append(']');
518             return s.toString();
519         }
520 
getCallDisconnectReasonString()521         private String getCallDisconnectReasonString() {
522             if (callTerminationReason != null) {
523                 return callTerminationReason.toString();
524             } else {
525                 return "NOT SET";
526             }
527         }
528 
getInCallServicesString()529         private String getInCallServicesString() {
530             StringBuilder s = new StringBuilder();
531             s.append("[\n");
532             for (TelecomLogClass.InCallServiceInfo service : inCallServiceInfos) {
533                 s.append("    ");
534                 s.append("name: ");
535                 s.append(service.getInCallServiceName());
536                 s.append(" type: ");
537                 s.append(service.getInCallServiceType());
538                 s.append(" is crashed: ");
539                 s.append(service.getIsNullBinding());
540                 s.append(" service last time in ms: ");
541                 s.append(service.getBoundDurationMillis());
542                 s.append("\n");
543             }
544             s.append("]");
545             return s.toString();
546         }
547 
getCallSourceString()548         private String getCallSourceString() {
549             switch (callSource) {
550                 case CALL_SOURCE_UNSPECIFIED:
551                     return "UNSPECIFIED";
552                 case CALL_SOURCE_EMERGENCY_DIALPAD:
553                     return "EMERGENCY_DIALPAD";
554                 case CALL_SOURCE_EMERGENCY_SHORTCUT:
555                     return "EMERGENCY_SHORTCUT";
556                 default:
557                     return "UNSPECIFIED";
558             }
559         }
560     }
561     public static final String TAG = "TelecomAnalytics";
562 
563     // Constants for call direction
564     public static final int UNKNOWN_DIRECTION = ParcelableCallAnalytics.CALLTYPE_UNKNOWN;
565     public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING;
566     public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING;
567 
568     // Constants for call technology
569     public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE;
570     public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE;
571     public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE;
572     public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
573     public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
574 
575     // Constants for call source
576     public static final int CALL_SOURCE_UNSPECIFIED =
577             TelecomManager.CALL_SOURCE_UNSPECIFIED;
578     public static final int CALL_SOURCE_EMERGENCY_DIALPAD =
579             TelecomManager.CALL_SOURCE_EMERGENCY_DIALPAD;
580     public static final int CALL_SOURCE_EMERGENCY_SHORTCUT =
581             TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT;
582 
583     // Constants for video events
584     public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST =
585             ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_REQUEST;
586     public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE =
587             ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_RESPONSE;
588     public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST =
589             ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST;
590     public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE =
591             ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE;
592 
593     public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
594 
595     public static final int MAX_NUM_CALLS_TO_STORE = 100;
596     public static final int MAX_NUM_DUMP_TIMES_TO_STORE = 100;
597 
598     private static final Object sLock = new Object(); // Coarse lock for all of analytics
599     private static final LinkedBlockingDeque<Long> sDumpTimes =
600             new LinkedBlockingDeque<>(MAX_NUM_DUMP_TIMES_TO_STORE);
601     private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
602     private static final LinkedList<String> sActiveCallIds = new LinkedList<>();
603     private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
604 
addSessionTiming(String sessionName, long time)605     public static void addSessionTiming(String sessionName, long time) {
606         if (sLogSessionToSessionId.containsKey(sessionName)) {
607             synchronized (sLock) {
608                 sSessionTimings.add(new SessionTiming(sLogSessionToSessionId.get(sessionName),
609                         time));
610             }
611         }
612     }
613 
initiateCallAnalytics(String callId, int direction)614     public static CallInfo initiateCallAnalytics(String callId, int direction) {
615         Log.d(TAG, "Starting analytics for call " + callId);
616         CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
617         synchronized (sLock) {
618             while (sActiveCallIds.size() >= MAX_NUM_CALLS_TO_STORE) {
619                 String callToRemove = sActiveCallIds.remove();
620                 sCallIdToInfo.remove(callToRemove);
621             }
622             sCallIdToInfo.put(callId, callInfo);
623             sActiveCallIds.add(callId);
624         }
625         return callInfo;
626     }
627 
dumpToParcelableAnalytics()628     public static TelecomAnalytics dumpToParcelableAnalytics() {
629         List<ParcelableCallAnalytics> calls = new LinkedList<>();
630         List<SessionTiming> sessionTimings = new LinkedList<>();
631         synchronized (sLock) {
632             calls.addAll(sCallIdToInfo.values().stream()
633                     .map(CallInfoImpl::toParcelableAnalytics)
634                     .collect(Collectors.toList()));
635             sessionTimings.addAll(sSessionTimings);
636             sCallIdToInfo.clear();
637             sSessionTimings.clear();
638         }
639         return new TelecomAnalytics(sessionTimings, calls);
640     }
641 
dumpToEncodedProto(Context context, PrintWriter pw, String[] args)642     public static void dumpToEncodedProto(Context context, PrintWriter pw, String[] args) {
643         TelecomLogClass.TelecomLog result = new TelecomLogClass.TelecomLog();
644 
645         synchronized (sLock) {
646             noteDumpTime();
647             result.callLogs = sCallIdToInfo.values().stream()
648                     .map(CallInfoImpl::toProto)
649                     .toArray(TelecomLogClass.CallLog[]::new);
650             result.sessionTimings = sSessionTimings.stream()
651                     .map(timing -> new TelecomLogClass.LogSessionTiming()
652                             .setSessionEntryPoint(timing.getKey())
653                             .setTimeMillis(timing.getTime()))
654                     .toArray(TelecomLogClass.LogSessionTiming[]::new);
655             result.setHardwareRevision(SystemProperties.get("ro.boot.revision", ""));
656             result.setCarrierId(getCarrierId(context));
657             if (args.length > 1 && CLEAR_ANALYTICS_ARG.equals(args[1])) {
658                 sCallIdToInfo.clear();
659                 sSessionTimings.clear();
660             }
661         }
662         String encodedProto = Base64.encodeToString(
663                 TelecomLogClass.TelecomLog.toByteArray(result), Base64.DEFAULT);
664         pw.write(encodedProto);
665     }
666 
getCarrierId(Context context)667     private static int getCarrierId(Context context) {
668         SubscriptionManager subscriptionManager =
669                 context.getSystemService(SubscriptionManager.class);
670         List<SubscriptionInfo> subInfos = subscriptionManager.getActiveSubscriptionInfoList();
671         if (subInfos == null) {
672             return -1;
673         }
674         return subInfos.stream()
675                 .max(Comparator.comparing(Analytics::scoreSubscriptionInfo))
676                 .map(SubscriptionInfo::getCarrierId).orElse(-1);
677     }
678 
679     // Copied over from Telephony's server-side logic for consistency
scoreSubscriptionInfo(SubscriptionInfo subInfo)680     private static int scoreSubscriptionInfo(SubscriptionInfo subInfo) {
681         final int scoreCarrierId = 0b100;
682         final int scoreNotOpportunistic = 0b010;
683         final int scoreSlot0 = 0b001;
684 
685         return ((subInfo.getCarrierId() >= 0) ? scoreCarrierId : 0)
686                 + (subInfo.isOpportunistic() ? 0 : scoreNotOpportunistic)
687                 + ((subInfo.getSimSlotIndex() == 0) ? scoreSlot0 : 0);
688     }
689 
dump(IndentingPrintWriter writer)690     public static void dump(IndentingPrintWriter writer) {
691         synchronized (sLock) {
692             int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length();
693             List<String> callIds = new ArrayList<>(sCallIdToInfo.keySet());
694             // Sort the analytics in increasing order of call IDs
695             try {
696                 Collections.sort(callIds, (id1, id2) -> {
697                     int i1, i2;
698                     try {
699                         i1 = Integer.valueOf(id1.substring(prefixLength));
700                     } catch (NumberFormatException e) {
701                         i1 = Integer.MAX_VALUE;
702                     }
703 
704                     try {
705                         i2 = Integer.valueOf(id2.substring(prefixLength));
706                     } catch (NumberFormatException e) {
707                         i2 = Integer.MAX_VALUE;
708                     }
709                     return i1 - i2;
710                 });
711             } catch (IllegalArgumentException e) {
712                 // do nothing, leave the list in a partially sorted state.
713             }
714 
715             for (String callId : callIds) {
716                 writer.printf("Call %s: ", callId);
717                 writer.println(sCallIdToInfo.get(callId).toString());
718             }
719 
720             Map<Integer, Double> averageTimings = SessionTiming.averageTimings(sSessionTimings);
721             averageTimings.entrySet().stream()
722                     .filter(e -> sSessionIdToLogSession.containsKey(e.getKey()))
723                     .forEach(e -> writer.printf("%s: %.2f\n",
724                             sSessionIdToLogSession.get(e.getKey()), e.getValue()));
725             writer.println("Hardware Version: " + SystemProperties.get("ro.boot.revision", ""));
726             writer.println("Past analytics dumps: ");
727             writer.increaseIndent();
728             for (long time : sDumpTimes) {
729                 writer.println(Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC));
730             }
731             writer.decreaseIndent();
732         }
733     }
734 
reset()735     public static void reset() {
736         synchronized (sLock) {
737             sCallIdToInfo.clear();
738         }
739     }
740 
noteDumpTime()741     public static void noteDumpTime() {
742         if (sDumpTimes.remainingCapacity() == 0) {
743             sDumpTimes.removeLast();
744         }
745         try {
746             sDumpTimes.addFirst(System.currentTimeMillis());
747         } catch (IllegalStateException e) {
748             Log.w(TAG, "Failed to note dump time -- full");
749         }
750     }
751 
752     /**
753      * Returns a copy of callIdToInfo. Use only for testing.
754      */
755     @VisibleForTesting
cloneData()756     public static Map<String, CallInfoImpl> cloneData() {
757         synchronized (sLock) {
758             Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size());
759             for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
760                 result.put(entry.getKey(), new CallInfoImpl(entry.getValue()));
761             }
762             return result;
763         }
764     }
765 
convertLogEventsToProtoEvents( List<EventManager.Event> logEvents)766     private static TelecomLogClass.Event[] convertLogEventsToProtoEvents(
767             List<EventManager.Event> logEvents) {
768         long timeOfLastEvent = -1;
769         ArrayList<TelecomLogClass.Event> events = new ArrayList<>(logEvents.size());
770         for (EventManager.Event logEvent : logEvents) {
771             if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) {
772                 TelecomLogClass.Event event = new TelecomLogClass.Event();
773                 event.setEventName(sLogEventToAnalyticsEvent.get(logEvent.eventId));
774                 event.setTimeSinceLastEventMillis(roundToOneSigFig(
775                         timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent));
776                 events.add(event);
777                 timeOfLastEvent = logEvent.time;
778             }
779         }
780         return events.toArray(new TelecomLogClass.Event[events.size()]);
781     }
782 
783     private static TelecomLogClass.EventTimingEntry logEventTimingToProtoEventTiming(
784             EventManager.EventRecord.EventTiming logEventTiming) {
785         int analyticsEventTimingName =
786                 sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ?
787                         sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) :
788                         ParcelableCallAnalytics.EventTiming.INVALID;
789         return new TelecomLogClass.EventTimingEntry()
790                 .setTimingName(analyticsEventTimingName)
791                 .setTimeMillis(logEventTiming.time);
792     }
793 
794     @VisibleForTesting
795     public static long roundToOneSigFig(long val)  {
796         if (val == 0) {
797             return val;
798         }
799         int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
800         double s = Math.pow(10, logVal);
801         double dec =  val / s;
802         return (long) (Math.round(dec) * s);
803     }
804 }
805