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