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