Home | History | Annotate | Download | only in telecom
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.server.telecom;
     18 
     19 import android.telecom.Connection;
     20 import android.telecom.DisconnectCause;
     21 import android.telecom.ParcelableCallAnalytics;
     22 import android.telecom.TelecomAnalytics;
     23 import android.util.Base64;
     24 
     25 import com.android.internal.annotations.VisibleForTesting;
     26 import com.android.internal.util.IndentingPrintWriter;
     27 
     28 import java.io.PrintWriter;
     29 import java.util.ArrayList;
     30 import java.util.Arrays;
     31 import java.util.Collections;
     32 import java.util.HashMap;
     33 import java.util.LinkedList;
     34 import java.util.List;
     35 import java.util.Map;
     36 import java.util.stream.Collectors;
     37 
     38 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
     39 import 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  */
     45 public 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