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 setMissedReason(long missedReason)212 public void setMissedReason(long missedReason) { 213 } 214 } 215 216 /** 217 * A class that holds data associated with a call. 218 */ 219 @VisibleForTesting 220 public static class CallInfoImpl extends CallInfo { 221 public String callId; 222 public long startTime; // start time in milliseconds since the epoch. 0 if not yet set. 223 public long endTime; // end time in milliseconds since the epoch. 0 if not yet set. 224 public int callDirection; // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION, 225 // or OUTGOING_DIRECTION. 226 public boolean isAdditionalCall = false; // true if the call came in while another call was 227 // in progress or if the user dialed this call 228 // while in the middle of another call. 229 public boolean isInterrupted = false; // true if the call was interrupted by an incoming 230 // or outgoing call. 231 public int callTechnologies; // bitmask denoting which technologies a call used. 232 233 // true if the Telecom Call object was created from an existing connection via 234 // CallsManager#createCallForExistingConnection, for example, by ImsConference. 235 public boolean createdFromExistingConnection = false; 236 237 public DisconnectCause callTerminationReason; 238 public String connectionService; 239 public boolean isEmergency = false; 240 241 public EventManager.EventRecord callEvents; 242 243 public boolean isVideo = false; 244 public List<TelecomLogClass.VideoEvent> videoEvents; 245 public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos; 246 public int callProperties = 0; 247 public int callSource = CALL_SOURCE_UNSPECIFIED; 248 public long missedReason; 249 250 private long mTimeOfLastVideoEvent = -1; 251 CallInfoImpl(String callId, int callDirection)252 CallInfoImpl(String callId, int callDirection) { 253 this.callId = callId; 254 startTime = 0; 255 endTime = 0; 256 this.callDirection = callDirection; 257 callTechnologies = 0; 258 connectionService = ""; 259 videoEvents = new LinkedList<>(); 260 inCallServiceInfos = new LinkedList<>(); 261 missedReason = 0; 262 } 263 CallInfoImpl(CallInfoImpl other)264 CallInfoImpl(CallInfoImpl other) { 265 this.callId = other.callId; 266 this.startTime = other.startTime; 267 this.endTime = other.endTime; 268 this.callDirection = other.callDirection; 269 this.isAdditionalCall = other.isAdditionalCall; 270 this.isInterrupted = other.isInterrupted; 271 this.callTechnologies = other.callTechnologies; 272 this.createdFromExistingConnection = other.createdFromExistingConnection; 273 this.connectionService = other.connectionService; 274 this.isEmergency = other.isEmergency; 275 this.callEvents = other.callEvents; 276 this.isVideo = other.isVideo; 277 this.videoEvents = other.videoEvents; 278 this.callProperties = other.callProperties; 279 this.callSource = other.callSource; 280 this.missedReason = other.missedReason; 281 282 if (other.callTerminationReason != null) { 283 this.callTerminationReason = new DisconnectCause( 284 other.callTerminationReason.getCode(), 285 other.callTerminationReason.getLabel(), 286 other.callTerminationReason.getDescription(), 287 other.callTerminationReason.getReason(), 288 other.callTerminationReason.getTone()); 289 } else { 290 this.callTerminationReason = null; 291 } 292 } 293 294 @Override setCallStartTime(long startTime)295 public void setCallStartTime(long startTime) { 296 Log.d(TAG, "setting startTime for call " + callId + " to " + startTime); 297 this.startTime = startTime; 298 } 299 300 @Override setCallEndTime(long endTime)301 public void setCallEndTime(long endTime) { 302 Log.d(TAG, "setting endTime for call " + callId + " to " + endTime); 303 this.endTime = endTime; 304 } 305 306 @Override setCallIsAdditional(boolean isAdditional)307 public void setCallIsAdditional(boolean isAdditional) { 308 Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional); 309 this.isAdditionalCall = isAdditional; 310 } 311 312 @Override setCallIsInterrupted(boolean isInterrupted)313 public void setCallIsInterrupted(boolean isInterrupted) { 314 Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted); 315 this.isInterrupted = isInterrupted; 316 } 317 318 @Override addCallTechnology(int callTechnology)319 public void addCallTechnology(int callTechnology) { 320 Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology); 321 this.callTechnologies |= callTechnology; 322 } 323 324 @Override setCallIsEmergency(boolean isEmergency)325 public void setCallIsEmergency(boolean isEmergency) { 326 Log.d(TAG, "setting call as emergency: " + isEmergency); 327 this.isEmergency = isEmergency; 328 } 329 330 @Override setCallDisconnectCause(DisconnectCause disconnectCause)331 public void setCallDisconnectCause(DisconnectCause disconnectCause) { 332 Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause); 333 this.callTerminationReason = disconnectCause; 334 } 335 336 @Override setCreatedFromExistingConnection(boolean createdFromExistingConnection)337 public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) { 338 Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to " 339 + createdFromExistingConnection); 340 this.createdFromExistingConnection = createdFromExistingConnection; 341 } 342 343 @Override setCallConnectionService(String connectionServiceName)344 public void setCallConnectionService(String connectionServiceName) { 345 Log.d(TAG, "setting connection service for call " + callId + ": " 346 + connectionServiceName); 347 this.connectionService = connectionServiceName; 348 } 349 350 @Override setMissedReason(long missedReason)351 public void setMissedReason(long missedReason) { 352 Log.d(TAG, "setting missedReason for call " + callId + ": " 353 + missedReason); 354 this.missedReason = missedReason; 355 } 356 357 @Override setCallEvents(EventManager.EventRecord records)358 public void setCallEvents(EventManager.EventRecord records) { 359 this.callEvents = records; 360 } 361 362 @Override setCallIsVideo(boolean isVideo)363 public void setCallIsVideo(boolean isVideo) { 364 this.isVideo = isVideo; 365 } 366 367 @Override addVideoEvent(int eventId, int videoState)368 public void addVideoEvent(int eventId, int videoState) { 369 long timeSinceLastEvent; 370 long currentTime = System.currentTimeMillis(); 371 if (mTimeOfLastVideoEvent < 0) { 372 timeSinceLastEvent = -1; 373 } else { 374 timeSinceLastEvent = roundToOneSigFig(currentTime - mTimeOfLastVideoEvent); 375 } 376 mTimeOfLastVideoEvent = currentTime; 377 378 videoEvents.add(new TelecomLogClass.VideoEvent() 379 .setEventName(eventId) 380 .setTimeSinceLastEventMillis(timeSinceLastEvent) 381 .setVideoState(videoState)); 382 } 383 384 @Override addInCallService(String serviceName, int type, long boundDuration, boolean isNullBinding)385 public void addInCallService(String serviceName, int type, long boundDuration, 386 boolean isNullBinding) { 387 inCallServiceInfos.add(new TelecomLogClass.InCallServiceInfo() 388 .setInCallServiceName(serviceName) 389 .setInCallServiceType(type) 390 .setBoundDurationMillis(boundDuration) 391 .setIsNullBinding(isNullBinding)); 392 } 393 394 @Override addCallProperties(int properties)395 public void addCallProperties(int properties) { 396 this.callProperties |= properties; 397 } 398 399 @Override setCallSource(int callSource)400 public void setCallSource(int callSource) { 401 this.callSource = callSource; 402 } 403 404 @Override toString()405 public String toString() { 406 return "{\n" 407 + " startTime: " + startTime + '\n' 408 + " endTime: " + endTime + '\n' 409 + " direction: " + getCallDirectionString() + '\n' 410 + " isAdditionalCall: " + isAdditionalCall + '\n' 411 + " isInterrupted: " + isInterrupted + '\n' 412 + " isEmergency: " + isEmergency + '\n' 413 + " callTechnologies: " + getCallTechnologiesAsString() + '\n' 414 + " callTerminationReason: " + getCallDisconnectReasonString() + '\n' 415 + " missedReason: " + getMissedReasonString() + '\n' 416 + " connectionService: " + connectionService + '\n' 417 + " isVideoCall: " + isVideo + '\n' 418 + " inCallServices: " + getInCallServicesString() + '\n' 419 + " callProperties: " + Connection.propertiesToStringShort(callProperties) 420 + '\n' 421 + " callSource: " + getCallSourceString() + '\n' 422 + "}\n"; 423 } 424 toParcelableAnalytics()425 public ParcelableCallAnalytics toParcelableAnalytics() { 426 TelecomLogClass.CallLog analyticsProto = toProto(); 427 List<ParcelableCallAnalytics.AnalyticsEvent> events = 428 Arrays.stream(analyticsProto.callEvents) 429 .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent( 430 callEventProto.getEventName(), 431 callEventProto.getTimeSinceLastEventMillis()) 432 ).collect(Collectors.toList()); 433 434 List<ParcelableCallAnalytics.EventTiming> timings = 435 Arrays.stream(analyticsProto.callTimings) 436 .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming( 437 callTimingProto.getTimingName(), 438 callTimingProto.getTimeMillis()) 439 ).collect(Collectors.toList()); 440 441 ParcelableCallAnalytics result = new ParcelableCallAnalytics( 442 // rounds down to nearest 5 minute mark 443 analyticsProto.getStartTime5Min(), 444 analyticsProto.getCallDurationMillis(), 445 analyticsProto.getType(), 446 analyticsProto.getIsAdditionalCall(), 447 analyticsProto.getIsInterrupted(), 448 analyticsProto.getCallTechnologies(), 449 analyticsProto.getCallTerminationCode(), 450 analyticsProto.getIsEmergencyCall(), 451 analyticsProto.connectionService[0], 452 analyticsProto.getIsCreatedFromExistingConnection(), 453 events, 454 timings); 455 456 result.setIsVideoCall(analyticsProto.getIsVideoCall()); 457 result.setVideoEvents(Arrays.stream(analyticsProto.videoEvents) 458 .map(videoEventProto -> new ParcelableCallAnalytics.VideoEvent( 459 videoEventProto.getEventName(), 460 videoEventProto.getTimeSinceLastEventMillis(), 461 videoEventProto.getVideoState()) 462 ).collect(Collectors.toList())); 463 464 result.setCallSource(analyticsProto.getCallSource()); 465 466 return result; 467 } 468 toProto()469 public TelecomLogClass.CallLog toProto() { 470 TelecomLogClass.CallLog result = new TelecomLogClass.CallLog(); 471 result.setStartTime5Min( 472 startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES); 473 474 // Rounds up to the nearest second. 475 long callDuration = (endTime == 0 || startTime == 0) ? 0 : endTime - startTime; 476 callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ? 477 0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND); 478 result.setCallDurationMillis(callDuration); 479 480 result.setType(callDirection) 481 .setIsAdditionalCall(isAdditionalCall) 482 .setIsInterrupted(isInterrupted) 483 .setCallTechnologies(callTechnologies) 484 .setCallTerminationCode( 485 callTerminationReason == null ? 486 ParcelableCallAnalytics.STILL_CONNECTED : 487 callTerminationReason.getCode()) 488 .setIsEmergencyCall(isEmergency) 489 .setIsCreatedFromExistingConnection(createdFromExistingConnection) 490 .setIsEmergencyCall(isEmergency) 491 .setIsVideoCall(isVideo) 492 .setConnectionProperties(callProperties) 493 .setCallSource(callSource); 494 495 result.connectionService = new String[] {connectionService}; 496 if (callEvents != null) { 497 result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents()); 498 result.callTimings = callEvents.extractEventTimings().stream() 499 .map(Analytics::logEventTimingToProtoEventTiming) 500 .toArray(TelecomLogClass.EventTimingEntry[]::new); 501 } 502 result.videoEvents = 503 videoEvents.toArray(new TelecomLogClass.VideoEvent[videoEvents.size()]); 504 result.inCallServices = inCallServiceInfos.toArray( 505 new TelecomLogClass.InCallServiceInfo[inCallServiceInfos.size()]); 506 507 return result; 508 } 509 getCallDirectionString()510 private String getCallDirectionString() { 511 switch (callDirection) { 512 case UNKNOWN_DIRECTION: 513 return "UNKNOWN"; 514 case INCOMING_DIRECTION: 515 return "INCOMING"; 516 case OUTGOING_DIRECTION: 517 return "OUTGOING"; 518 default: 519 return "UNKNOWN"; 520 } 521 } 522 getCallTechnologiesAsString()523 private String getCallTechnologiesAsString() { 524 StringBuilder s = new StringBuilder(); 525 s.append('['); 526 if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA "); 527 if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM "); 528 if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP "); 529 if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS "); 530 if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY "); 531 s.append(']'); 532 return s.toString(); 533 } 534 getCallDisconnectReasonString()535 private String getCallDisconnectReasonString() { 536 if (callTerminationReason != null) { 537 return callTerminationReason.toString(); 538 } else { 539 return "NOT SET"; 540 } 541 } 542 getMissedReasonString()543 private String getMissedReasonString() { 544 //TODO: Implement this 545 return null; 546 } 547 getInCallServicesString()548 private String getInCallServicesString() { 549 StringBuilder s = new StringBuilder(); 550 s.append("[\n"); 551 if (inCallServiceInfos != null) { 552 for (TelecomLogClass.InCallServiceInfo service : inCallServiceInfos) { 553 s.append(" "); 554 s.append("name: "); 555 s.append(service.getInCallServiceName()); 556 s.append(" type: "); 557 s.append(service.getInCallServiceType()); 558 s.append(" is crashed: "); 559 s.append(service.getIsNullBinding()); 560 s.append(" service last time in ms: "); 561 s.append(service.getBoundDurationMillis()); 562 s.append("\n"); 563 } 564 } 565 s.append("]"); 566 return s.toString(); 567 } 568 getCallSourceString()569 private String getCallSourceString() { 570 switch (callSource) { 571 case CALL_SOURCE_UNSPECIFIED: 572 return "UNSPECIFIED"; 573 case CALL_SOURCE_EMERGENCY_DIALPAD: 574 return "EMERGENCY_DIALPAD"; 575 case CALL_SOURCE_EMERGENCY_SHORTCUT: 576 return "EMERGENCY_SHORTCUT"; 577 default: 578 return "UNSPECIFIED"; 579 } 580 } 581 } 582 public static final String TAG = "TelecomAnalytics"; 583 584 // Constants for call direction 585 public static final int UNKNOWN_DIRECTION = ParcelableCallAnalytics.CALLTYPE_UNKNOWN; 586 public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING; 587 public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING; 588 589 // Constants for call technology 590 public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE; 591 public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE; 592 public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE; 593 public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE; 594 public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE; 595 596 // Constants for call source 597 public static final int CALL_SOURCE_UNSPECIFIED = 598 TelecomManager.CALL_SOURCE_UNSPECIFIED; 599 public static final int CALL_SOURCE_EMERGENCY_DIALPAD = 600 TelecomManager.CALL_SOURCE_EMERGENCY_DIALPAD; 601 public static final int CALL_SOURCE_EMERGENCY_SHORTCUT = 602 TelecomManager.CALL_SOURCE_EMERGENCY_SHORTCUT; 603 604 // Constants for video events 605 public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST = 606 ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_REQUEST; 607 public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE = 608 ParcelableCallAnalytics.VideoEvent.SEND_LOCAL_SESSION_MODIFY_RESPONSE; 609 public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST = 610 ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST; 611 public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE = 612 ParcelableCallAnalytics.VideoEvent.RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE; 613 614 public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND; 615 616 public static final int MAX_NUM_CALLS_TO_STORE = 100; 617 public static final int MAX_NUM_DUMP_TIMES_TO_STORE = 100; 618 619 private static final Object sLock = new Object(); // Coarse lock for all of analytics 620 private static final LinkedBlockingDeque<Long> sDumpTimes = 621 new LinkedBlockingDeque<>(MAX_NUM_DUMP_TIMES_TO_STORE); 622 private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>(); 623 private static final LinkedList<String> sActiveCallIds = new LinkedList<>(); 624 private static final List<SessionTiming> sSessionTimings = new LinkedList<>(); 625 addSessionTiming(String sessionName, long time)626 public static void addSessionTiming(String sessionName, long time) { 627 if (sLogSessionToSessionId.containsKey(sessionName)) { 628 synchronized (sLock) { 629 sSessionTimings.add(new SessionTiming(sLogSessionToSessionId.get(sessionName), 630 time)); 631 } 632 } 633 } 634 initiateCallAnalytics(String callId, int direction)635 public static CallInfo initiateCallAnalytics(String callId, int direction) { 636 Log.i(TAG, "Starting analytics for call " + callId); 637 CallInfoImpl callInfo = new CallInfoImpl(callId, direction); 638 synchronized (sLock) { 639 while (sActiveCallIds.size() >= MAX_NUM_CALLS_TO_STORE) { 640 String callToRemove = sActiveCallIds.remove(); 641 sCallIdToInfo.remove(callToRemove); 642 } 643 sCallIdToInfo.put(callId, callInfo); 644 sActiveCallIds.add(callId); 645 } 646 return callInfo; 647 } 648 dumpToParcelableAnalytics()649 public static TelecomAnalytics dumpToParcelableAnalytics() { 650 List<ParcelableCallAnalytics> calls = new LinkedList<>(); 651 List<SessionTiming> sessionTimings = new LinkedList<>(); 652 synchronized (sLock) { 653 calls.addAll(sCallIdToInfo.values().stream() 654 .map(CallInfoImpl::toParcelableAnalytics) 655 .collect(Collectors.toList())); 656 sessionTimings.addAll(sSessionTimings); 657 sCallIdToInfo.clear(); 658 sSessionTimings.clear(); 659 } 660 return new TelecomAnalytics(sessionTimings, calls); 661 } 662 dumpToEncodedProto(Context context, PrintWriter pw, String[] args)663 public static void dumpToEncodedProto(Context context, PrintWriter pw, String[] args) { 664 TelecomLogClass.TelecomLog result = new TelecomLogClass.TelecomLog(); 665 666 synchronized (sLock) { 667 noteDumpTime(); 668 result.callLogs = sCallIdToInfo.values().stream() 669 .map(CallInfoImpl::toProto) 670 .toArray(TelecomLogClass.CallLog[]::new); 671 result.sessionTimings = sSessionTimings.stream() 672 .map(timing -> new TelecomLogClass.LogSessionTiming() 673 .setSessionEntryPoint(timing.getKey()) 674 .setTimeMillis(timing.getTime())) 675 .toArray(TelecomLogClass.LogSessionTiming[]::new); 676 result.setHardwareRevision(SystemProperties.get("ro.boot.revision", "")); 677 result.setCarrierId(getCarrierId(context)); 678 if (args.length > 1 && CLEAR_ANALYTICS_ARG.equals(args[1])) { 679 sCallIdToInfo.clear(); 680 sSessionTimings.clear(); 681 } 682 } 683 String encodedProto = Base64.encodeToString( 684 TelecomLogClass.TelecomLog.toByteArray(result), Base64.DEFAULT); 685 pw.write(encodedProto); 686 } 687 getCarrierId(Context context)688 private static int getCarrierId(Context context) { 689 SubscriptionManager subscriptionManager = 690 context.getSystemService(SubscriptionManager.class); 691 List<SubscriptionInfo> subInfos = subscriptionManager.getActiveSubscriptionInfoList(); 692 if (subInfos == null) { 693 return -1; 694 } 695 return subInfos.stream() 696 .max(Comparator.comparing(Analytics::scoreSubscriptionInfo)) 697 .map(SubscriptionInfo::getCarrierId).orElse(-1); 698 } 699 700 // Copied over from Telephony's server-side logic for consistency scoreSubscriptionInfo(SubscriptionInfo subInfo)701 private static int scoreSubscriptionInfo(SubscriptionInfo subInfo) { 702 final int scoreCarrierId = 0b100; 703 final int scoreNotOpportunistic = 0b010; 704 final int scoreSlot0 = 0b001; 705 706 return ((subInfo.getCarrierId() >= 0) ? scoreCarrierId : 0) 707 + (subInfo.isOpportunistic() ? 0 : scoreNotOpportunistic) 708 + ((subInfo.getSimSlotIndex() == 0) ? scoreSlot0 : 0); 709 } 710 dump(IndentingPrintWriter writer)711 public static void dump(IndentingPrintWriter writer) { 712 synchronized (sLock) { 713 int prefixLength = CallsManager.TELECOM_CALL_ID_PREFIX.length(); 714 List<String> callIds = new ArrayList<>(sCallIdToInfo.keySet()); 715 // Sort the analytics in increasing order of call IDs 716 try { 717 Collections.sort(callIds, (id1, id2) -> { 718 int i1, i2; 719 try { 720 i1 = Integer.valueOf(id1.substring(prefixLength)); 721 } catch (NumberFormatException e) { 722 i1 = Integer.MAX_VALUE; 723 } 724 725 try { 726 i2 = Integer.valueOf(id2.substring(prefixLength)); 727 } catch (NumberFormatException e) { 728 i2 = Integer.MAX_VALUE; 729 } 730 return i1 - i2; 731 }); 732 } catch (IllegalArgumentException e) { 733 // do nothing, leave the list in a partially sorted state. 734 } 735 736 for (String callId : callIds) { 737 writer.printf("Call %s: ", callId); 738 writer.println(sCallIdToInfo.get(callId).toString()); 739 } 740 741 Map<Integer, Double> averageTimings = SessionTiming.averageTimings(sSessionTimings); 742 averageTimings.entrySet().stream() 743 .filter(e -> sSessionIdToLogSession.containsKey(e.getKey())) 744 .forEach(e -> writer.printf("%s: %.2f\n", 745 sSessionIdToLogSession.get(e.getKey()), e.getValue())); 746 writer.println("Hardware Version: " + SystemProperties.get("ro.boot.revision", "")); 747 writer.println("Past analytics dumps: "); 748 writer.increaseIndent(); 749 for (long time : sDumpTimes) { 750 writer.println(Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC)); 751 } 752 writer.decreaseIndent(); 753 } 754 } 755 reset()756 public static void reset() { 757 synchronized (sLock) { 758 sCallIdToInfo.clear(); 759 } 760 } 761 noteDumpTime()762 public static void noteDumpTime() { 763 if (sDumpTimes.remainingCapacity() == 0) { 764 sDumpTimes.removeLast(); 765 } 766 try { 767 sDumpTimes.addFirst(System.currentTimeMillis()); 768 } catch (IllegalStateException e) { 769 Log.w(TAG, "Failed to note dump time -- full"); 770 } 771 } 772 773 /** 774 * Returns a copy of callIdToInfo. Use only for testing. 775 */ 776 @VisibleForTesting cloneData()777 public static Map<String, CallInfoImpl> cloneData() { 778 synchronized (sLock) { 779 Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size()); 780 for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) { 781 result.put(entry.getKey(), new CallInfoImpl(entry.getValue())); 782 } 783 return result; 784 } 785 } 786 convertLogEventsToProtoEvents( List<EventManager.Event> logEvents)787 private static TelecomLogClass.Event[] convertLogEventsToProtoEvents( 788 List<EventManager.Event> logEvents) { 789 long timeOfLastEvent = -1; 790 ArrayList<TelecomLogClass.Event> events = new ArrayList<>(logEvents.size()); 791 for (EventManager.Event logEvent : logEvents) { 792 if (sLogEventToAnalyticsEvent.containsKey(logEvent.eventId)) { 793 TelecomLogClass.Event event = new TelecomLogClass.Event(); 794 event.setEventName(sLogEventToAnalyticsEvent.get(logEvent.eventId)); 795 event.setTimeSinceLastEventMillis(roundToOneSigFig( 796 timeOfLastEvent < 0 ? -1 : logEvent.time - timeOfLastEvent)); 797 events.add(event); 798 timeOfLastEvent = logEvent.time; 799 } 800 } 801 return events.toArray(new TelecomLogClass.Event[events.size()]); 802 } 803 804 private static TelecomLogClass.EventTimingEntry logEventTimingToProtoEventTiming( 805 EventManager.EventRecord.EventTiming logEventTiming) { 806 int analyticsEventTimingName = 807 sLogEventTimingToAnalyticsEventTiming.containsKey(logEventTiming.name) ? 808 sLogEventTimingToAnalyticsEventTiming.get(logEventTiming.name) : 809 ParcelableCallAnalytics.EventTiming.INVALID; 810 return new TelecomLogClass.EventTimingEntry() 811 .setTimingName(analyticsEventTimingName) 812 .setTimeMillis(logEventTiming.time); 813 } 814 815 @VisibleForTesting 816 public static long roundToOneSigFig(long val) { 817 if (val == 0) { 818 return val; 819 } 820 int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val)); 821 double s = Math.pow(10, logVal); 822 double dec = val / s; 823 return (long) (Math.round(dec) * s); 824 } 825 } 826