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