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