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