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