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