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