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