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