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