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