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