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