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