• 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.telecom.DisconnectCause;
20 import android.telecom.ParcelableCallAnalytics;
21 import android.telecom.TelecomAnalytics;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 import com.android.internal.util.IndentingPrintWriter;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.stream.Collectors;
33 
34 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
35 import static android.telecom.TelecomAnalytics.SessionTiming;
36 
37 /**
38  * A class that collects and stores data on how calls are being made, in order to
39  * aggregate these into useful statistics.
40  */
41 public class Analytics {
42     public static final Map<String, Integer> sLogEventToAnalyticsEvent =
43             new HashMap<String, Integer>() {{
44                 put(Log.Events.SET_SELECT_PHONE_ACCOUNT, AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT);
45                 put(Log.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD);
46                 put(Log.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD);
47                 put(Log.Events.SWAP, AnalyticsEvent.SWAP);
48                 put(Log.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING);
49                 put(Log.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH);
50                 put(Log.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE);
51                 put(Log.Events.SET_PARENT, AnalyticsEvent.SET_PARENT);
52                 put(Log.Events.MUTE, AnalyticsEvent.MUTE);
53                 put(Log.Events.UNMUTE, AnalyticsEvent.UNMUTE);
54                 put(Log.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT);
55                 put(Log.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE);
56                 put(Log.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET);
57                 put(Log.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER);
58                 put(Log.Events.SILENCE, AnalyticsEvent.SILENCE);
59                 put(Log.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED);
60                 put(Log.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED);
61                 put(Log.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED);
62                 put(Log.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD);
63                 put(Log.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD);
64                 put(Log.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL);
65                 put(Log.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT);
66                 put(Log.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT);
67                 put(Log.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE);
68                 put(Log.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED);
69                 put(Log.Events.SET_HOLD, AnalyticsEvent.SET_HOLD);
70                 put(Log.Events.SET_DIALING, AnalyticsEvent.SET_DIALING);
71                 put(Log.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION);
72                 put(Log.Events.BIND_CS, AnalyticsEvent.BIND_CS);
73                 put(Log.Events.CS_BOUND, AnalyticsEvent.CS_BOUND);
74                 put(Log.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT);
75                 put(Log.Events.DIRECT_TO_VM_INITIATED, AnalyticsEvent.DIRECT_TO_VM_INITIATED);
76                 put(Log.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED);
77                 put(Log.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED);
78                 put(Log.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED);
79                 put(Log.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT);
80             }};
81 
82     public static final Map<String, Integer> sLogSessionToSessionId =
83             new HashMap<String, Integer> () {{
84                 put(Log.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL);
85                 put(Log.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL);
86                 put(Log.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL);
87                 put(Log.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL);
88                 put(Log.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL);
89                 put(Log.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE);
90                 put(Log.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE);
91                 put(Log.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE);
92                 put(Log.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
93                         SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
94                 put(Log.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE);
95                 put(Log.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING);
96                 put(Log.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING);
97                 put(Log.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED);
98                 put(Log.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD);
99                 put(Log.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL);
100                 put(Log.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED);
101                 put(Log.Sessions.CSW_ADD_CONFERENCE_CALL, SessionTiming.CSW_ADD_CONFERENCE_CALL);
102 
103             }};
104 
105     public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming =
106             new HashMap<String, Integer>() {{
107                 put(Log.Events.Timings.ACCEPT_TIMING,
108                         ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING);
109                 put(Log.Events.Timings.REJECT_TIMING,
110                         ParcelableCallAnalytics.EventTiming.REJECT_TIMING);
111                 put(Log.Events.Timings.DISCONNECT_TIMING,
112                         ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING);
113                 put(Log.Events.Timings.HOLD_TIMING,
114                         ParcelableCallAnalytics.EventTiming.HOLD_TIMING);
115                 put(Log.Events.Timings.UNHOLD_TIMING,
116                         ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING);
117                 put(Log.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
118                         ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING);
119                 put(Log.Events.Timings.BIND_CS_TIMING,
120                         ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING);
121                 put(Log.Events.Timings.SCREENING_COMPLETED_TIMING,
122                         ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING);
123                 put(Log.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
124                         ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING);
125                 put(Log.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
126                         ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING);
127                 put(Log.Events.Timings.FILTERING_COMPLETED_TIMING,
128                         ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
129                 put(Log.Events.Timings.FILTERING_TIMED_OUT_TIMING,
130                         ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
131             }};
132 
133     public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
134     static {
135         for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
e.getValue()136             sSessionIdToLogSession.put(e.getValue(), e.getKey());
137         }
138     }
139 
140     public static class CallInfo {
setCallStartTime(long startTime)141         public void setCallStartTime(long startTime) {
142         }
143 
setCallEndTime(long endTime)144         public void setCallEndTime(long endTime) {
145         }
146 
setCallIsAdditional(boolean isAdditional)147         public void setCallIsAdditional(boolean isAdditional) {
148         }
149 
setCallIsInterrupted(boolean isInterrupted)150         public void setCallIsInterrupted(boolean isInterrupted) {
151         }
152 
setCallDisconnectCause(DisconnectCause disconnectCause)153         public void setCallDisconnectCause(DisconnectCause disconnectCause) {
154         }
155 
addCallTechnology(int callTechnology)156         public void addCallTechnology(int callTechnology) {
157         }
158 
setCreatedFromExistingConnection(boolean createdFromExistingConnection)159         public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
160         }
161 
setCallConnectionService(String connectionServiceName)162         public void setCallConnectionService(String connectionServiceName) {
163         }
164 
setCallEvents(Log.CallEventRecord records)165         public void setCallEvents(Log.CallEventRecord records) {
166         }
167 
setCallIsVideo(boolean isVideo)168         public void setCallIsVideo(boolean isVideo) {
169         }
170 
addVideoEvent(int eventId, int videoState)171         public void addVideoEvent(int eventId, int videoState) {
172         }
173     }
174 
175     /**
176      * A class that holds data associated with a call.
177      */
178     @VisibleForTesting
179     public static class CallInfoImpl extends CallInfo {
180         public String callId;
181         public long startTime;  // start time in milliseconds since the epoch. 0 if not yet set.
182         public long endTime;  // end time in milliseconds since the epoch. 0 if not yet set.
183         public int callDirection;  // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
184                                    // or OUTGOING_DIRECTION.
185         public boolean isAdditionalCall = false;  // true if the call came in while another call was
186                                                   // in progress or if the user dialed this call
187                                                   // while in the middle of another call.
188         public boolean isInterrupted = false;  // true if the call was interrupted by an incoming
189                                                // or outgoing call.
190         public int callTechnologies;  // bitmask denoting which technologies a call used.
191 
192         // true if the Telecom Call object was created from an existing connection via
193         // CallsManager#createCallForExistingConnection, for example, by ImsConference.
194         public boolean createdFromExistingConnection = false;
195 
196         public DisconnectCause callTerminationReason;
197         public String connectionService;
198         public boolean isEmergency = false;
199 
200         public Log.CallEventRecord callEvents;
201 
202         public boolean isVideo = false;
203         public List<ParcelableCallAnalytics.VideoEvent> videoEvents;
204         private long mTimeOfLastVideoEvent = -1;
205 
CallInfoImpl(String callId, int callDirection)206         CallInfoImpl(String callId, int callDirection) {
207             this.callId = callId;
208             startTime = 0;
209             endTime = 0;
210             this.callDirection = callDirection;
211             callTechnologies = 0;
212             connectionService = "";
213             videoEvents = new LinkedList<>();
214         }
215 
CallInfoImpl(CallInfoImpl other)216         CallInfoImpl(CallInfoImpl other) {
217             this.callId = other.callId;
218             this.startTime = other.startTime;
219             this.endTime = other.endTime;
220             this.callDirection = other.callDirection;
221             this.isAdditionalCall = other.isAdditionalCall;
222             this.isInterrupted = other.isInterrupted;
223             this.callTechnologies = other.callTechnologies;
224             this.createdFromExistingConnection = other.createdFromExistingConnection;
225             this.connectionService = other.connectionService;
226             this.isEmergency = other.isEmergency;
227             this.callEvents = other.callEvents;
228             this.isVideo = other.isVideo;
229             this.videoEvents = other.videoEvents;
230 
231             if (other.callTerminationReason != null) {
232                 this.callTerminationReason = new DisconnectCause(
233                         other.callTerminationReason.getCode(),
234                         other.callTerminationReason.getLabel(),
235                         other.callTerminationReason.getDescription(),
236                         other.callTerminationReason.getReason(),
237                         other.callTerminationReason.getTone());
238             } else {
239                 this.callTerminationReason = null;
240             }
241         }
242 
243         @Override
setCallStartTime(long startTime)244         public void setCallStartTime(long startTime) {
245             Log.d(TAG, "setting startTime for call " + callId + " to " + startTime);
246             this.startTime = startTime;
247         }
248 
249         @Override
setCallEndTime(long endTime)250         public void setCallEndTime(long endTime) {
251             Log.d(TAG, "setting endTime for call " + callId + " to " + endTime);
252             this.endTime = endTime;
253         }
254 
255         @Override
setCallIsAdditional(boolean isAdditional)256         public void setCallIsAdditional(boolean isAdditional) {
257             Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional);
258             this.isAdditionalCall = isAdditional;
259         }
260 
261         @Override
setCallIsInterrupted(boolean isInterrupted)262         public void setCallIsInterrupted(boolean isInterrupted) {
263             Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted);
264             this.isInterrupted = isInterrupted;
265         }
266 
267         @Override
addCallTechnology(int callTechnology)268         public void addCallTechnology(int callTechnology) {
269             Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology);
270             this.callTechnologies |= callTechnology;
271         }
272 
273         @Override
setCallDisconnectCause(DisconnectCause disconnectCause)274         public void setCallDisconnectCause(DisconnectCause disconnectCause) {
275             Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
276             this.callTerminationReason = disconnectCause;
277         }
278 
279         @Override
setCreatedFromExistingConnection(boolean createdFromExistingConnection)280         public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
281             Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to "
282                     + createdFromExistingConnection);
283             this.createdFromExistingConnection = createdFromExistingConnection;
284         }
285 
286         @Override
setCallConnectionService(String connectionServiceName)287         public void setCallConnectionService(String connectionServiceName) {
288             Log.d(TAG, "setting connection service for call " + callId + ": "
289                     + connectionServiceName);
290             this.connectionService = connectionServiceName;
291         }
292 
293         @Override
setCallEvents(Log.CallEventRecord records)294         public void setCallEvents(Log.CallEventRecord records) {
295             this.callEvents = records;
296         }
297 
298         @Override
setCallIsVideo(boolean isVideo)299         public void setCallIsVideo(boolean isVideo) {
300             this.isVideo = isVideo;
301         }
302 
303         @Override
addVideoEvent(int eventId, int videoState)304         public void addVideoEvent(int eventId, int videoState) {
305             long timeSinceLastEvent;
306             long currentTime = System.currentTimeMillis();
307             if (mTimeOfLastVideoEvent < 0) {
308                 timeSinceLastEvent = -1;
309             } else {
310                 timeSinceLastEvent = roundToOneSigFig(currentTime - mTimeOfLastVideoEvent);
311             }
312             mTimeOfLastVideoEvent = currentTime;
313 
314             videoEvents.add(new ParcelableCallAnalytics.VideoEvent(
315                     eventId, timeSinceLastEvent, videoState));
316         }
317 
318         @Override
toString()319         public String toString() {
320             return "{\n"
321                     + "    startTime: " + startTime + '\n'
322                     + "    endTime: " + endTime + '\n'
323                     + "    direction: " + getCallDirectionString() + '\n'
324                     + "    isAdditionalCall: " + isAdditionalCall + '\n'
325                     + "    isInterrupted: " + isInterrupted + '\n'
326                     + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
327                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
328                     + "    connectionService: " + connectionService + '\n'
329                     + "    isVideoCall: " + isVideo + '\n'
330                     + "}\n";
331         }
332 
toParcelableAnalytics()333         public ParcelableCallAnalytics toParcelableAnalytics() {
334             // Rounds up to the nearest second.
335             long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime;
336             callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ?
337                     0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND);
338 
339             List<AnalyticsEvent> events;
340             List<ParcelableCallAnalytics.EventTiming> timings;
341             if (callEvents != null) {
342                 events = convertLogEventsToAnalyticsEvents(callEvents.getEvents());
343                 timings = callEvents.extractEventTimings().stream()
344                         .map(Analytics::logEventTimingToAnalyticsEventTiming)
345                         .collect(Collectors.toList());
346             } else {
347                 events = Collections.emptyList();
348                 timings = Collections.emptyList();
349             }
350             ParcelableCallAnalytics result = new ParcelableCallAnalytics(
351                     // rounds down to nearest 5 minute mark
352                     startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES,
353                     callDuration,
354                     callDirection,
355                     isAdditionalCall,
356                     isInterrupted,
357                     callTechnologies,
358                     callTerminationReason == null ?
359                             ParcelableCallAnalytics.STILL_CONNECTED :
360                             callTerminationReason.getCode(),
361                     isEmergency,
362                     connectionService,
363                     createdFromExistingConnection,
364                     events,
365                     timings);
366             result.setIsVideoCall(isVideo);
367             result.setVideoEvents(videoEvents);
368             return result;
369         }
370 
getCallDirectionString()371         private String getCallDirectionString() {
372             switch (callDirection) {
373                 case UNKNOWN_DIRECTION:
374                     return "UNKNOWN";
375                 case INCOMING_DIRECTION:
376                     return "INCOMING";
377                 case OUTGOING_DIRECTION:
378                     return "OUTGOING";
379                 default:
380                     return "UNKNOWN";
381             }
382         }
383 
getCallTechnologiesAsString()384         private String getCallTechnologiesAsString() {
385             StringBuilder s = new StringBuilder();
386             s.append('[');
387             if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA ");
388             if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM ");
389             if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP ");
390             if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS ");
391             if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY ");
392             s.append(']');
393             return s.toString();
394         }
395 
getCallDisconnectReasonString()396         private String getCallDisconnectReasonString() {
397             if (callTerminationReason != null) {
398                 return callTerminationReason.toString();
399             } else {
400                 return "NOT SET";
401             }
402         }
403     }
404     public static final String TAG = "TelecomAnalytics";
405 
406     // Constants for call direction
407     public static final int UNKNOWN_DIRECTION = ParcelableCallAnalytics.CALLTYPE_UNKNOWN;
408     public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING;
409     public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING;
410 
411     // Constants for call technology
412     public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE;
413     public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE;
414     public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE;
415     public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
416     public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
417 
418     // Constants for video events
419     public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST =
420             ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_REQUEST;
421     public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE =
422             ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_RESPONSE;
423     public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST =
424             ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST;
425     public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE =
426             ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE;
427 
428     public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
429 
430     private static final Object sLock = new Object(); // Coarse lock for all of analytics
431     private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
432     private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
433 
addSessionTiming(String sessionName, long time)434     public static void addSessionTiming(String sessionName, long time) {
435         if (sLogSessionToSessionId.containsKey(sessionName)) {
436             synchronized (sLock) {
437                 sSessionTimings.add(new SessionTiming(sLogSessionToSessionId.get(sessionName),
438                         time));
439             }
440         }
441     }
442 
initiateCallAnalytics(String callId, int direction)443     public static CallInfo initiateCallAnalytics(String callId, int direction) {
444         Log.d(TAG, "Starting analytics for call " + callId);
445         CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
446         synchronized (sLock) {
447             sCallIdToInfo.put(callId, callInfo);
448         }
449         return callInfo;
450     }
451 
dumpToParcelableAnalytics()452     public static TelecomAnalytics dumpToParcelableAnalytics() {
453         List<ParcelableCallAnalytics> calls = new LinkedList<>();
454         List<SessionTiming> sessionTimings = new LinkedList<>();
455         synchronized (sLock) {
456             calls.addAll(sCallIdToInfo.values().stream()
457                     .map(CallInfoImpl::toParcelableAnalytics)
458                     .collect(Collectors.toList()));
459             sessionTimings.addAll(sSessionTimings);
460             sCallIdToInfo.clear();
461             sSessionTimings.clear();
462         }
463         return new TelecomAnalytics(sessionTimings, calls);
464     }
465 
dump(IndentingPrintWriter writer)466     public static void dump(IndentingPrintWriter writer) {
467         synchronized (sLock) {
468             int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length();
469             List<String> callIds = new ArrayList<>(sCallIdToInfo.keySet());
470             // Sort the analytics in increasing order of call IDs
471             try {
472                 Collections.sort(callIds, (id1, id2) -> {
473                     int i1, i2;
474                     try {
475                         i1 = Integer.valueOf(id1.substring(prefixLength));
476                     } catch (NumberFormatException e) {
477                         i1 = Integer.MAX_VALUE;
478                     }
479 
480                     try {
481                         i2 = Integer.valueOf(id2.substring(prefixLength));
482                     } catch (NumberFormatException e) {
483                         i2 = Integer.MAX_VALUE;
484                     }
485                     return i1 - i2;
486                 });
487             } catch (IllegalArgumentException e) {
488                 // do nothing, leave the list in a partially sorted state.
489             }
490 
491             for (String callId : callIds) {
492                 writer.printf("Call %s: ", callId);
493                 writer.println(sCallIdToInfo.get(callId).toString());
494             }
495 
496             Map<Integer, Double> averageTimings = SessionTiming.averageTimings(sSessionTimings);
497             averageTimings.entrySet().stream()
498                     .filter(e -> sSessionIdToLogSession.containsKey(e.getKey()))
499                     .forEach(e -> writer.printf("%s: %.2f\n",
500                             sSessionIdToLogSession.get(e.getKey()), e.getValue()));
501         }
502     }
503 
reset()504     public static void reset() {
505         synchronized (sLock) {
506             sCallIdToInfo.clear();
507         }
508     }
509 
510     /**
511      * Returns a copy of callIdToInfo. Use only for testing.
512      */
513     @VisibleForTesting
cloneData()514     public static Map<String, CallInfoImpl> cloneData() {
515         synchronized (sLock) {
516             Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size());
517             for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
518                 result.put(entry.getKey(), new CallInfoImpl(entry.getValue()));
519             }
520             return result;
521         }
522     }
523 
convertLogEventsToAnalyticsEvents( List<Log.CallEvent> logEvents)524     private static List<AnalyticsEvent> convertLogEventsToAnalyticsEvents(
525             List<Log.CallEvent> logEvents) {
526         long timeOfLastEvent = -1;
527         ArrayList<AnalyticsEvent> events = new ArrayList<>(logEvents.size());
528         for (Log.CallEvent logEvent : logEvents) {
529             if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) {
530                 int analyticsEventId = sLogEventToAnalyticsEvent.get(logEvent.eventId);
531                 long timeSinceLastEvent =
532                         timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent;
533                 events.add(new AnalyticsEvent(
534                         analyticsEventId,
535                         roundToOneSigFig(timeSinceLastEvent)
536                 ));
537                 timeOfLastEvent = logEvent.time;
538             }
539         }
540         return events;
541     }
542 
543     private static ParcelableCallAnalytics.EventTiming logEventTimingToAnalyticsEventTiming(
544             Log.CallEventRecord.EventTiming logEventTiming) {
545         int analyticsEventTimingName =
546                 sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ?
547                         sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) :
548                         ParcelableCallAnalytics.EventTiming.INVALID;
549         return new ParcelableCallAnalytics.EventTiming(analyticsEventTimingName,
550                 (long) logEventTiming.time);
551     }
552 
553     @VisibleForTesting
554     public static long roundToOneSigFig(long val)  {
555         if (val == 0) {
556             return val;
557         }
558         int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
559         double s = Math.pow(10, logVal);
560         double dec =  val / s;
561         return (long) (Math.round(dec) * s);
562     }
563 }
564