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