1 /* 2 * Copyright 2014, 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.net.Uri; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.AsyncTask; 24 import android.telecom.PhoneAccount; 25 import android.telecom.TimedEvent; 26 import android.telephony.PhoneNumberUtils; 27 import android.text.TextUtils; 28 import android.util.Base64; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.IndentingPrintWriter; 32 33 import java.nio.ByteBuffer; 34 import java.security.MessageDigest; 35 import java.security.NoSuchAlgorithmException; 36 import java.text.DateFormat; 37 import java.text.SimpleDateFormat; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.Date; 42 import java.util.HashMap; 43 import java.util.IllegalFormatException; 44 import java.util.Iterator; 45 import java.util.LinkedList; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.Map; 49 import java.util.concurrent.ConcurrentHashMap; 50 import java.util.concurrent.LinkedBlockingQueue; 51 52 /** 53 * Manages logging for the entire module. 54 */ 55 @VisibleForTesting 56 public class Log { 57 58 public static final class Sessions { 59 public static final String ICA_ANSWER_CALL = "ICA.aC"; 60 public static final String ICA_REJECT_CALL = "ICA.rC"; 61 public static final String ICA_DISCONNECT_CALL = "ICA.dC"; 62 public static final String ICA_HOLD_CALL = "ICA.hC"; 63 public static final String ICA_UNHOLD_CALL = "ICA.uC"; 64 public static final String ICA_MUTE = "ICA.m"; 65 public static final String ICA_SET_AUDIO_ROUTE = "ICA.sAR"; 66 public static final String ICA_CONFERENCE = "ICA.c"; 67 public static final String CSW_HANDLE_CREATE_CONNECTION_COMPLETE = "CSW.hCCC"; 68 public static final String CSW_SET_ACTIVE = "CSW.sA"; 69 public static final String CSW_SET_RINGING = "CSW.sR"; 70 public static final String CSW_SET_DIALING = "CSW.sD"; 71 public static final String CSW_SET_PULLING = "CSW.sP"; 72 public static final String CSW_SET_DISCONNECTED = "CSW.sDc"; 73 public static final String CSW_SET_ON_HOLD = "CSW.sOH"; 74 public static final String CSW_REMOVE_CALL = "CSW.rC"; 75 public static final String CSW_SET_IS_CONFERENCED = "CSW.sIC"; 76 public static final String CSW_ADD_CONFERENCE_CALL = "CSW.aCC"; 77 } 78 79 /** 80 * Stores the various events associated with {@link Call}s. Also stores all request-response 81 * pairs amongst the events. 82 */ 83 public final static class Events { 84 public static class TimedEventPair { 85 private static final long DEFAULT_TIMEOUT = 3000L; 86 87 String mRequest; 88 String mResponse; 89 String mName; 90 long mTimeoutMillis = DEFAULT_TIMEOUT; 91 92 public TimedEventPair(String request, String response, 93 String name) { 94 this.mRequest = request; 95 this.mResponse = response; 96 this.mName = name; 97 } 98 99 public TimedEventPair(String request, String response, 100 String name, long timeoutMillis) { 101 this.mRequest = request; 102 this.mResponse = response; 103 this.mName = name; 104 this.mTimeoutMillis = timeoutMillis; 105 } 106 } 107 108 public static final String CREATED = "CREATED"; 109 public static final String DESTROYED = "DESTROYED"; 110 public static final String SET_CONNECTING = "SET_CONNECTING"; 111 public static final String SET_DIALING = "SET_DIALING"; 112 public static final String SET_PULLING = "SET_PULLING"; 113 public static final String SET_ACTIVE = "SET_ACTIVE"; 114 public static final String SET_HOLD = "SET_HOLD"; 115 public static final String SET_RINGING = "SET_RINGING"; 116 public static final String SET_DISCONNECTED = "SET_DISCONNECTED"; 117 public static final String SET_DISCONNECTING = "SET_DISCONNECTING"; 118 public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT"; 119 public static final String REQUEST_HOLD = "REQUEST_HOLD"; 120 public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD"; 121 public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT"; 122 public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT"; 123 public static final String REQUEST_REJECT = "REQUEST_REJECT"; 124 public static final String START_DTMF = "START_DTMF"; 125 public static final String STOP_DTMF = "STOP_DTMF"; 126 public static final String START_RINGER = "START_RINGER"; 127 public static final String STOP_RINGER = "STOP_RINGER"; 128 public static final String SKIP_RINGING = "SKIP_RINGING"; 129 public static final String START_CALL_WAITING_TONE = "START_CALL_WAITING_TONE"; 130 public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE"; 131 public static final String START_CONNECTION = "START_CONNECTION"; 132 public static final String BIND_CS = "BIND_CS"; 133 public static final String CS_BOUND = "CS_BOUND"; 134 public static final String CONFERENCE_WITH = "CONF_WITH"; 135 public static final String SPLIT_FROM_CONFERENCE = "CONF_SPLIT"; 136 public static final String SWAP = "SWAP"; 137 public static final String ADD_CHILD = "ADD_CHILD"; 138 public static final String REMOVE_CHILD = "REMOVE_CHILD"; 139 public static final String SET_PARENT = "SET_PARENT"; 140 public static final String MUTE = "MUTE"; 141 public static final String UNMUTE = "UNMUTE"; 142 public static final String AUDIO_ROUTE = "AUDIO_ROUTE"; 143 public static final String AUDIO_ROUTE_EARPIECE = "AUDIO_ROUTE_EARPIECE"; 144 public static final String AUDIO_ROUTE_HEADSET = "AUDIO_ROUTE_HEADSET"; 145 public static final String AUDIO_ROUTE_BT = "AUDIO_ROUTE_BT"; 146 public static final String AUDIO_ROUTE_SPEAKER = "AUDIO_ROUTE_SPEAKER"; 147 public static final String ERROR_LOG = "ERROR"; 148 public static final String USER_LOG_MARK = "USER_LOG_MARK"; 149 public static final String SILENCE = "SILENCE"; 150 public static final String BIND_SCREENING = "BIND_SCREENING"; 151 public static final String SCREENING_BOUND = "SCREENING_BOUND"; 152 public static final String SCREENING_SENT = "SCREENING_SENT"; 153 public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED"; 154 public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED"; 155 public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED"; 156 public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED"; 157 public static final String DIRECT_TO_VM_FINISHED = "DIRECT_TO_VM_FINISHED"; 158 public static final String FILTERING_INITIATED = "FILTERING_INITIATED"; 159 public static final String FILTERING_COMPLETED = "FILTERING_COMPLETED"; 160 public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT"; 161 public static final String REMOTELY_HELD = "REMOTELY_HELD"; 162 public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD"; 163 public static final String REQUEST_PULL = "PULL"; 164 public static final String INFO = "INFO"; 165 public static final String VIDEO_STATE_CHANGED = "VIDEO_STATE_CHANGED"; 166 public static final String RECEIVE_VIDEO_REQUEST = "RECEIVE_VIDEO_REQUEST"; 167 public static final String RECEIVE_VIDEO_RESPONSE = "RECEIVE_VIDEO_RESPONSE"; 168 public static final String SEND_VIDEO_REQUEST = "SEND_VIDEO_REQUEST"; 169 public static final String SEND_VIDEO_RESPONSE = "SEND_VIDEO_RESPONSE"; 170 public static final String IS_EXTERNAL = "IS_EXTERNAL"; 171 public static final String PROPERTY_CHANGE = "PROPERTY_CHANGE"; 172 public static final String CAPABILITY_CHANGE = "CAPABILITY_CHANGE"; 173 174 public static class Timings { 175 public static final String ACCEPT_TIMING = "accept"; 176 public static final String REJECT_TIMING = "reject"; 177 public static final String DISCONNECT_TIMING = "disconnect"; 178 public static final String HOLD_TIMING = "hold"; 179 public static final String UNHOLD_TIMING = "unhold"; 180 public static final String OUTGOING_TIME_TO_DIALING_TIMING = "outgoing_time_to_dialing"; 181 public static final String BIND_CS_TIMING = "bind_cs"; 182 public static final String SCREENING_COMPLETED_TIMING = "screening_completed"; 183 public static final String DIRECT_TO_VM_FINISHED_TIMING = "direct_to_vm_finished"; 184 public static final String BLOCK_CHECK_FINISHED_TIMING = "block_check_finished"; 185 public static final String FILTERING_COMPLETED_TIMING = "filtering_completed"; 186 public static final String FILTERING_TIMED_OUT_TIMING = "filtering_timed_out"; 187 188 private static final TimedEventPair[] sTimedEvents = { 189 new TimedEventPair(REQUEST_ACCEPT, SET_ACTIVE, ACCEPT_TIMING), 190 new TimedEventPair(REQUEST_REJECT, SET_DISCONNECTED, REJECT_TIMING), 191 new TimedEventPair(REQUEST_DISCONNECT, SET_DISCONNECTED, DISCONNECT_TIMING), 192 new TimedEventPair(REQUEST_HOLD, SET_HOLD, HOLD_TIMING), 193 new TimedEventPair(REQUEST_UNHOLD, SET_ACTIVE, UNHOLD_TIMING), 194 new TimedEventPair(START_CONNECTION, SET_DIALING, 195 OUTGOING_TIME_TO_DIALING_TIMING), 196 new TimedEventPair(BIND_CS, CS_BOUND, BIND_CS_TIMING), 197 new TimedEventPair(SCREENING_SENT, SCREENING_COMPLETED, 198 SCREENING_COMPLETED_TIMING), 199 new TimedEventPair(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED, 200 DIRECT_TO_VM_FINISHED_TIMING), 201 new TimedEventPair(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED, 202 BLOCK_CHECK_FINISHED_TIMING), 203 new TimedEventPair(FILTERING_INITIATED, FILTERING_COMPLETED, 204 FILTERING_COMPLETED_TIMING), 205 new TimedEventPair(FILTERING_INITIATED, FILTERING_TIMED_OUT, 206 FILTERING_TIMED_OUT_TIMING, 6000L), 207 }; 208 } 209 210 /** 211 * Maps from request events to a list of possible response events. Used to track 212 * end-to-end timing for critical user-facing operations in Telecom. 213 */ 214 public static final Map<String, List<TimedEventPair>> requestResponsePairs; 215 static { 216 requestResponsePairs = new HashMap<>(); 217 for (TimedEventPair p : Timings.sTimedEvents) { 218 if (requestResponsePairs.containsKey(p.mRequest)) { 219 requestResponsePairs.get(p.mRequest).add(p); 220 } else { 221 ArrayList<TimedEventPair> responses = new ArrayList<>(); 222 responses.add(p); 223 requestResponsePairs.put(p.mRequest, responses); 224 } 225 } 226 } 227 } 228 229 public static class CallEvent { 230 public String eventId; 231 public String sessionId; 232 public long time; 233 public Object data; 234 235 public CallEvent(String eventId, String sessionId, long time, Object data) { 236 this.eventId = eventId; 237 this.sessionId = sessionId; 238 this.time = time; 239 this.data = data; 240 } 241 } 242 243 public static class CallEventRecord { 244 public static class EventTiming extends TimedEvent<String> { 245 public String name; 246 public long time; 247 248 public EventTiming(String name, long time) { 249 this.name = name; 250 this.time = time; 251 } 252 253 public String getKey() { 254 return name; 255 } 256 257 public long getTime() { 258 return time; 259 } 260 } 261 262 private static class PendingResponse { 263 String requestEventId; 264 long requestEventTimeMillis; 265 long timeoutMillis; 266 String name; 267 268 public PendingResponse(String requestEventId, long requestEventTimeMillis, 269 long timeoutMillis, String name) { 270 this.requestEventId = requestEventId; 271 this.requestEventTimeMillis = requestEventTimeMillis; 272 this.timeoutMillis = timeoutMillis; 273 this.name = name; 274 } 275 } 276 277 private static final DateFormat sLongDateFormat = new SimpleDateFormat( 278 "yyyy-MM-dd HH:mm:ss.SSS"); 279 private static final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); 280 private final List<CallEvent> mEvents = new LinkedList<>(); 281 private final Call mCall; 282 283 public CallEventRecord(Call call) { 284 mCall = call; 285 } 286 287 public Call getCall() { 288 return mCall; 289 } 290 291 public void addEvent(String event, String sessionId, Object data) { 292 mEvents.add(new CallEvent(event, sessionId, System.currentTimeMillis(), data)); 293 Log.i("Event", "Call %s: %s, %s", mCall.getId(), event, data); 294 } 295 296 public List<CallEvent> getEvents() { 297 return mEvents; 298 } 299 300 public List<EventTiming> extractEventTimings() { 301 if (mEvents == null) { 302 return Collections.emptyList(); 303 } 304 305 LinkedList<EventTiming> result = new LinkedList<>(); 306 Map<String, PendingResponse> pendingResponses = new HashMap<>(); 307 for (CallEvent event : mEvents) { 308 if (Events.requestResponsePairs.containsKey(event.eventId)) { 309 // This event expects a response, so add that expected response to the maps 310 // of pending events. 311 for (Events.TimedEventPair p : Events.requestResponsePairs.get(event.eventId)) { 312 pendingResponses.put(p.mResponse, new PendingResponse(event.eventId, 313 event.time, p.mTimeoutMillis, p.mName)); 314 } 315 } 316 317 PendingResponse pendingResponse = pendingResponses.remove(event.eventId); 318 if (pendingResponse != null) { 319 long elapsedTime = event.time - pendingResponse.requestEventTimeMillis; 320 if (elapsedTime < pendingResponse.timeoutMillis) { 321 result.add(new EventTiming(pendingResponse.name, elapsedTime)); 322 } 323 } 324 } 325 326 return result; 327 } 328 329 public void dump(IndentingPrintWriter pw) { 330 pw.print("Call "); 331 pw.print(mCall.getId()); 332 pw.print(" ["); 333 pw.print(sLongDateFormat.format(new Date(mCall.getCreationTimeMillis()))); 334 pw.print("]"); 335 pw.println(mCall.isIncoming() ? "(MT - incoming)" : "(MO - outgoing)"); 336 337 pw.increaseIndent(); 338 pw.println("To address: " + piiHandle(mCall.getHandle())); 339 340 for (CallEvent event : mEvents) { 341 pw.print(sDateFormat.format(new Date(event.time))); 342 pw.print(" - "); 343 pw.print(event.eventId); 344 if (event.data != null) { 345 pw.print(" ("); 346 Object data = event.data; 347 348 if (data instanceof Call) { 349 // If the data is another call, then change the data to the call's CallEvent 350 // ID instead. 351 CallEventRecord record = mCallEventRecordMap.get(data); 352 if (record != null) { 353 data = "Call " + record.mCall.getId(); 354 } 355 } 356 357 pw.print(data); 358 pw.print(")"); 359 } 360 pw.print(":"); 361 pw.print(event.sessionId); 362 pw.println(); 363 } 364 365 pw.println("Timings (average for this call, milliseconds):"); 366 pw.increaseIndent(); 367 Map<String, Double> avgEventTimings = EventTiming.averageTimings(extractEventTimings()); 368 List<String> eventNames = new ArrayList<>(avgEventTimings.keySet()); 369 Collections.sort(eventNames); 370 for (String eventName : eventNames) { 371 pw.printf("%s: %.2f\n", eventName, avgEventTimings.get(eventName)); 372 } 373 pw.decreaseIndent(); 374 pw.decreaseIndent(); 375 } 376 } 377 378 public static final int MAX_CALLS_TO_CACHE = 10; // Arbitrarily chosen. 379 public static final int MAX_CALLS_TO_CACHE_DEBUG = 20; // Arbitrarily chosen. 380 private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes 381 382 // Don't check in with this true! 383 private static final boolean LOG_DBG = false; 384 385 // Currently using 3 letters, So don't exceed 64^3 386 private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144; 387 388 // Generic tag for all In Call logging 389 @VisibleForTesting 390 public static String TAG = "Telecom"; 391 public static String LOGGING_TAG = "Logging"; 392 393 public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */ 394 public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */ 395 public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG); 396 public static final boolean INFO = isLoggable(android.util.Log.INFO); 397 public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE); 398 public static final boolean WARN = isLoggable(android.util.Log.WARN); 399 public static final boolean ERROR = isLoggable(android.util.Log.ERROR); 400 401 private static final Map<Call, CallEventRecord> mCallEventRecordMap = new HashMap<>(); 402 private static LinkedBlockingQueue<CallEventRecord> mCallEventRecords = 403 new LinkedBlockingQueue<CallEventRecord>(MAX_CALLS_TO_CACHE); 404 405 private static Context mContext = null; 406 // Synchronized in all method calls 407 private static int sCodeEntryCounter = 0; 408 @VisibleForTesting 409 public static ConcurrentHashMap<Integer, Session> sSessionMapper = new ConcurrentHashMap<>(100); 410 @VisibleForTesting 411 public static Handler sSessionCleanupHandler = new Handler(Looper.getMainLooper()); 412 @VisibleForTesting 413 public static java.lang.Runnable sCleanStaleSessions = new java.lang.Runnable() { 414 @Override 415 public void run() { 416 cleanupStaleSessions(getSessionCleanupTimeoutMs()); 417 } 418 }; 419 420 // Set the logging container to be the system's. This will only change when being mocked 421 // during testing. 422 private static SystemLoggingContainer systemLogger = new SystemLoggingContainer(); 423 424 /** 425 * Tracks whether user-activated extended logging is enabled. 426 */ 427 private static boolean mIsUserExtendedLoggingEnabled = false; 428 429 /** 430 * The time when user-activated extended logging should be ended. Used to determine when 431 * extended logging should automatically be disabled. 432 */ 433 private static long mUserExtendedLoggingStopTime = 0; 434 435 private Log() { 436 } 437 438 public static void setContext(Context context) { 439 mContext = context; 440 } 441 442 /** 443 * Enable or disable extended telecom logging. 444 * 445 * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled, 446 * {@code false} if it should be disabled. 447 */ 448 public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) { 449 // If the state hasn't changed, bail early. 450 if (mIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) { 451 return; 452 } 453 454 // Resize the event queue. 455 int newSize = isExtendedLoggingEnabled ? MAX_CALLS_TO_CACHE_DEBUG : MAX_CALLS_TO_CACHE; 456 LinkedBlockingQueue<CallEventRecord> oldEventLog = mCallEventRecords; 457 mCallEventRecords = new LinkedBlockingQueue<CallEventRecord>(newSize); 458 mCallEventRecordMap.clear(); 459 460 // Copy the existing queue into the new one. 461 for (CallEventRecord event : oldEventLog) { 462 addCallEventRecord(event); 463 } 464 465 mIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled; 466 if (mIsUserExtendedLoggingEnabled) { 467 mUserExtendedLoggingStopTime = System.currentTimeMillis() 468 + EXTENDED_LOGGING_DURATION_MILLIS; 469 } else { 470 mUserExtendedLoggingStopTime = 0; 471 } 472 } 473 474 public static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds 475 private static MessageDigest sMessageDigest; 476 477 public static void initMd5Sum() { 478 new AsyncTask<Void, Void, Void>() { 479 @Override 480 public Void doInBackground(Void... args) { 481 MessageDigest md; 482 try { 483 md = MessageDigest.getInstance("SHA-1"); 484 } catch (NoSuchAlgorithmException e) { 485 md = null; 486 } 487 sMessageDigest = md; 488 return null; 489 } 490 }.execute(); 491 } 492 493 @VisibleForTesting 494 public static void setTag(String tag) { 495 TAG = tag; 496 } 497 498 @VisibleForTesting 499 public static void setLoggingContainer(SystemLoggingContainer logger) { 500 systemLogger = logger; 501 } 502 503 // Overridden in LogTest to skip query to ContentProvider 504 public interface ISessionCleanupTimeoutMs { 505 long get(); 506 } 507 @VisibleForTesting 508 public static ISessionCleanupTimeoutMs sSessionCleanupTimeoutMs = 509 new ISessionCleanupTimeoutMs() { 510 @Override 511 public long get() { 512 // mContext will be null if Log is called from another process 513 // (UserCallActivity, for example). For these cases, use the default value. 514 if(mContext == null) { 515 return DEFAULT_SESSION_TIMEOUT_MS; 516 } 517 return Timeouts.getStaleSessionCleanupTimeoutMillis( 518 mContext.getContentResolver()); 519 } 520 }; 521 522 private static long getSessionCleanupTimeoutMs() { 523 return sSessionCleanupTimeoutMs.get(); 524 } 525 526 private static synchronized void resetStaleSessionTimer() { 527 sSessionCleanupHandler.removeCallbacksAndMessages(null); 528 // Will be null in Log Testing 529 if (sCleanStaleSessions != null) { 530 sSessionCleanupHandler.postDelayed(sCleanStaleSessions, getSessionCleanupTimeoutMs()); 531 } 532 } 533 534 /** 535 * Call at an entry point to the Telecom code to track the session. This code must be 536 * accompanied by a Log.endSession(). 537 */ 538 public static synchronized void startSession(String shortMethodName) { 539 startSession(shortMethodName, null); 540 } 541 public static synchronized void startSession(String shortMethodName, 542 String callerIdentification) { 543 resetStaleSessionTimer(); 544 int threadId = getCallingThreadId(); 545 Session activeSession = sSessionMapper.get(threadId); 546 // We have called startSession within an active session that has not ended... Register this 547 // session as a subsession. 548 if (activeSession != null) { 549 Session childSession = createSubsession(true); 550 continueSession(childSession, shortMethodName); 551 return; 552 } 553 Session newSession = new Session(getNextSessionID(), shortMethodName, 554 System.currentTimeMillis(), threadId, false, callerIdentification); 555 sSessionMapper.put(threadId, newSession); 556 557 Log.v(LOGGING_TAG, Session.START_SESSION); 558 } 559 560 561 /** 562 * Notifies the logging system that a subsession will be run at a later point and 563 * allocates the resources. Returns a session object that must be used in 564 * Log.continueSession(...) to start the subsession. 565 */ 566 public static Session createSubsession() { 567 return createSubsession(false); 568 } 569 570 private static synchronized Session createSubsession(boolean isStartedFromActiveSession) { 571 int threadId = getCallingThreadId(); 572 Session threadSession = sSessionMapper.get(threadId); 573 if (threadSession == null) { 574 Log.d(LOGGING_TAG, "Log.createSubsession was called with no session active."); 575 return null; 576 } 577 // Start execution time of the session will be overwritten in continueSession(...). 578 Session newSubsession = new Session(threadSession.getNextChildId(), 579 threadSession.getShortMethodName(), System.currentTimeMillis(), threadId, 580 isStartedFromActiveSession, null); 581 threadSession.addChild(newSubsession); 582 newSubsession.setParentSession(threadSession); 583 584 if(!isStartedFromActiveSession) { 585 Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + newSubsession.toString()); 586 } else { 587 Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)"); 588 } 589 return newSubsession; 590 } 591 592 /** 593 * Cancels a subsession that had Log.createSubsession() called on it, but will never have 594 * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned 595 * gracefully instead of being removed by the sSessionCleanupHandler forcefully later. 596 */ 597 public static synchronized void cancelSubsession(Session subsession) { 598 if (subsession == null) { 599 return; 600 } 601 602 subsession.markSessionCompleted(0); 603 endParentSessions(subsession); 604 } 605 606 /** 607 * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method 608 * must be called at the end of this method. The full session will complete when all 609 * subsessions are completed. 610 */ 611 public static synchronized void continueSession(Session subsession, String shortMethodName) { 612 if (subsession == null) { 613 return; 614 } 615 resetStaleSessionTimer(); 616 String callingMethodName = subsession.getShortMethodName(); 617 subsession.setShortMethodName(callingMethodName + "->" + shortMethodName); 618 subsession.setExecutionStartTimeMs(System.currentTimeMillis()); 619 Session parentSession = subsession.getParentSession(); 620 if (parentSession == null) { 621 Log.d(LOGGING_TAG, "Log.continueSession was called with no session active for " + 622 "method %s.", shortMethodName); 623 return; 624 } 625 626 sSessionMapper.put(getCallingThreadId(), subsession); 627 if(!subsession.isStartedFromActiveSession()) { 628 Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION); 629 } else { 630 Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION + " (Invisible Subsession) with " + 631 "Method " + shortMethodName); 632 } 633 } 634 635 public static void checkIsThreadLogged() { 636 int threadId = getCallingThreadId(); 637 Session threadSession = sSessionMapper.get(threadId); 638 if (threadSession == null) { 639 android.util.Log.e(LOGGING_TAG, "Logging Thread Check Failed!", new Exception()); 640 } 641 } 642 643 /** 644 * Ends the current session/subsession. Must be called after a Log.startSession(...) and 645 * Log.continueSession(...) call. 646 */ 647 public static synchronized void endSession() { 648 int threadId = getCallingThreadId(); 649 Session completedSession = sSessionMapper.get(threadId); 650 if (completedSession == null) { 651 Log.w(LOGGING_TAG, "Log.endSession was called with no session active."); 652 return; 653 } 654 655 completedSession.markSessionCompleted(System.currentTimeMillis()); 656 if(!completedSession.isStartedFromActiveSession()) { 657 Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " + 658 completedSession.getLocalExecutionTime() + " mS)"); 659 } else { 660 Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (Invisible Subsession) (dur: " + 661 completedSession.getLocalExecutionTime() + " mS)"); 662 } 663 // Remove after completed so that reference still exists for logging the end events 664 Session parentSession = completedSession.getParentSession(); 665 sSessionMapper.remove(threadId); 666 endParentSessions(completedSession); 667 // If this subsession was started from a parent session using Log.startSession, return the 668 // ThreadID back to the parent after completion. 669 if (parentSession != null && !parentSession.isSessionCompleted() && 670 completedSession.isStartedFromActiveSession()) { 671 sSessionMapper.put(threadId, parentSession); 672 } 673 } 674 675 // Recursively deletes all complete parent sessions of the current subsession if it is a leaf. 676 private static void endParentSessions(Session subsession) { 677 // Session is not completed or not currently a leaf, so we can not remove because a child is 678 // still running 679 if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) { 680 return; 681 } 682 683 Session parentSession = subsession.getParentSession(); 684 if (parentSession != null) { 685 subsession.setParentSession(null); 686 parentSession.removeChild(subsession); 687 endParentSessions(parentSession); 688 } else { 689 // All of the subsessions have been completed and it is time to report on the full 690 // running time of the session. 691 long fullSessionTimeMs = 692 System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds(); 693 Analytics.addSessionTiming(subsession.getShortMethodName(), fullSessionTimeMs); 694 Log.v(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + " ms): " + 695 subsession.toString()); 696 } 697 } 698 699 private synchronized static String getNextSessionID() { 700 Integer nextId = sCodeEntryCounter++; 701 if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) { 702 restartSessionCounter(); 703 nextId = sCodeEntryCounter++; 704 } 705 return getBase64Encoding(nextId); 706 } 707 708 @VisibleForTesting 709 public synchronized static void restartSessionCounter() { 710 sCodeEntryCounter = 0; 711 } 712 713 @VisibleForTesting 714 public static String getBase64Encoding(int number) { 715 byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array(); 716 idByteArray = Arrays.copyOfRange(idByteArray, 2, 4); 717 return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING); 718 } 719 720 public static int getCallingThreadId() { 721 return android.os.Process.myTid(); 722 } 723 724 public static void event(Call call, String event) { 725 event(call, event, null); 726 } 727 728 public static void event(Call call, String event, Object data) { 729 Session currentSession = sSessionMapper.get(getCallingThreadId()); 730 String currentSessionID = currentSession != null ? currentSession.toString() : ""; 731 732 if (call == null) { 733 Log.i(TAG, "Non-call EVENT: %s, %s", event, data); 734 return; 735 } 736 synchronized (mCallEventRecords) { 737 if (!mCallEventRecordMap.containsKey(call)) { 738 CallEventRecord newRecord = new CallEventRecord(call); 739 addCallEventRecord(newRecord); 740 } 741 742 CallEventRecord record = mCallEventRecordMap.get(call); 743 record.addEvent(event, currentSessionID, data); 744 } 745 } 746 747 public static void event(Call call, String event, String format, Object... args) { 748 String msg; 749 try { 750 msg = (args == null || args.length == 0) ? format 751 : String.format(Locale.US, format, args); 752 } catch (IllegalFormatException ife) { 753 e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format, 754 args.length); 755 msg = format + " (An error occurred while formatting the message.)"; 756 } 757 758 event(call, event, msg); 759 } 760 761 @VisibleForTesting 762 public static synchronized void cleanupStaleSessions(long timeoutMs) { 763 String logMessage = "Stale Sessions Cleaned:\n"; 764 boolean isSessionsStale = false; 765 long currentTimeMs = System.currentTimeMillis(); 766 // Remove references that are in the Session Mapper (causing GC to occur) on 767 // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS. 768 // If this occurs, then there is most likely a Session active that never had 769 // Log.endSession called on it. 770 for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it = 771 sSessionMapper.entrySet().iterator(); it.hasNext(); ) { 772 ConcurrentHashMap.Entry<Integer, Session> entry = it.next(); 773 Session session = entry.getValue(); 774 if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) { 775 it.remove(); 776 logMessage += session.printFullSessionTree() + "\n"; 777 isSessionsStale = true; 778 } 779 } 780 if (isSessionsStale) { 781 Log.w(LOGGING_TAG, logMessage); 782 } else { 783 Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned..."); 784 } 785 } 786 787 private static void addCallEventRecord(CallEventRecord newRecord) { 788 Call call = newRecord.getCall(); 789 790 // First remove the oldest entry if no new ones exist. 791 if (mCallEventRecords.remainingCapacity() == 0) { 792 CallEventRecord record = mCallEventRecords.poll(); 793 if (record != null) { 794 mCallEventRecordMap.remove(record.getCall()); 795 } 796 } 797 798 // Now add a new entry 799 mCallEventRecords.add(newRecord); 800 mCallEventRecordMap.put(call, newRecord); 801 802 // Register the events with Analytics 803 if (call.getAnalytics() != null) { 804 call.getAnalytics().setCallEvents(newRecord); 805 } else { 806 Log.w(LOGGING_TAG, "Call analytics is null"); 807 } 808 } 809 810 /** 811 * If user enabled extended logging is enabled and the time limit has passed, disables the 812 * extended logging. 813 */ 814 private static void maybeDisableLogging() { 815 if (!mIsUserExtendedLoggingEnabled) { 816 return; 817 } 818 819 if (mUserExtendedLoggingStopTime < System.currentTimeMillis()) { 820 mUserExtendedLoggingStopTime = 0; 821 mIsUserExtendedLoggingEnabled = false; 822 } 823 } 824 825 public static boolean isLoggable(int level) { 826 return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); 827 } 828 829 public static void d(String prefix, String format, Object... args) { 830 if (mIsUserExtendedLoggingEnabled) { 831 maybeDisableLogging(); 832 systemLogger.i(TAG, buildMessage(prefix, format, args)); 833 } else if (DEBUG) { 834 systemLogger.d(TAG, buildMessage(prefix, format, args)); 835 } 836 } 837 838 public static void d(Object objectPrefix, String format, Object... args) { 839 if (mIsUserExtendedLoggingEnabled) { 840 maybeDisableLogging(); 841 systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 842 } else if (DEBUG) { 843 systemLogger.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 844 } 845 } 846 847 public static void i(String prefix, String format, Object... args) { 848 if (INFO) { 849 systemLogger.i(TAG, buildMessage(prefix, format, args)); 850 } 851 } 852 853 public static void i(Object objectPrefix, String format, Object... args) { 854 if (INFO) { 855 systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 856 } 857 } 858 859 public static void v(String prefix, String format, Object... args) { 860 if (mIsUserExtendedLoggingEnabled) { 861 maybeDisableLogging(); 862 systemLogger.i(TAG, buildMessage(prefix, format, args)); 863 } else if (VERBOSE) { 864 systemLogger.v(TAG, buildMessage(prefix, format, args)); 865 } 866 } 867 868 public static void v(Object objectPrefix, String format, Object... args) { 869 if (mIsUserExtendedLoggingEnabled) { 870 maybeDisableLogging(); 871 systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 872 } else if (VERBOSE) { 873 systemLogger.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 874 } 875 } 876 877 public static void w(String prefix, String format, Object... args) { 878 if (WARN) { 879 systemLogger.w(TAG, buildMessage(prefix, format, args)); 880 } 881 } 882 883 public static void w(Object objectPrefix, String format, Object... args) { 884 if (WARN) { 885 systemLogger.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 886 } 887 } 888 889 public static void e(String prefix, Throwable tr, String format, Object... args) { 890 if (ERROR) { 891 systemLogger.e(TAG, buildMessage(prefix, format, args), tr); 892 } 893 } 894 895 public static void e(Object objectPrefix, Throwable tr, String format, Object... args) { 896 if (ERROR) { 897 systemLogger.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 898 tr); 899 } 900 } 901 902 public static void wtf(String prefix, Throwable tr, String format, Object... args) { 903 systemLogger.wtf(TAG, buildMessage(prefix, format, args), tr); 904 } 905 906 public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) { 907 systemLogger.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 908 tr); 909 } 910 911 public static void wtf(String prefix, String format, Object... args) { 912 String msg = buildMessage(prefix, format, args); 913 systemLogger.wtf(TAG, msg, new IllegalStateException(msg)); 914 } 915 916 public static void wtf(Object objectPrefix, String format, Object... args) { 917 String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args); 918 systemLogger.wtf(TAG, msg, new IllegalStateException(msg)); 919 } 920 921 public static String piiHandle(Object pii) { 922 if (pii == null || VERBOSE) { 923 return String.valueOf(pii); 924 } 925 926 StringBuilder sb = new StringBuilder(); 927 if (pii instanceof Uri) { 928 Uri uri = (Uri) pii; 929 String scheme = uri.getScheme(); 930 931 if (!TextUtils.isEmpty(scheme)) { 932 sb.append(scheme).append(":"); 933 } 934 935 String textToObfuscate = uri.getSchemeSpecificPart(); 936 if (PhoneAccount.SCHEME_TEL.equals(scheme)) { 937 for (int i = 0; i < textToObfuscate.length(); i++) { 938 char c = textToObfuscate.charAt(i); 939 sb.append(PhoneNumberUtils.isDialable(c) ? "*" : c); 940 } 941 } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 942 for (int i = 0; i < textToObfuscate.length(); i++) { 943 char c = textToObfuscate.charAt(i); 944 if (c != '@' && c != '.') { 945 c = '*'; 946 } 947 sb.append(c); 948 } 949 } else { 950 sb.append(pii(pii)); 951 } 952 } 953 954 return sb.toString(); 955 } 956 957 /** 958 * Redact personally identifiable information for production users. 959 * If we are running in verbose mode, return the original string, otherwise 960 * return a SHA-1 hash of the input string. 961 */ 962 public static String pii(Object pii) { 963 if (pii == null || VERBOSE) { 964 return String.valueOf(pii); 965 } 966 return "[" + secureHash(String.valueOf(pii).getBytes()) + "]"; 967 } 968 969 public static void dumpCallEvents(IndentingPrintWriter pw) { 970 pw.println("Historical Calls:"); 971 pw.increaseIndent(); 972 for (CallEventRecord callEventRecord : mCallEventRecords) { 973 callEventRecord.dump(pw); 974 } 975 pw.decreaseIndent(); 976 } 977 978 private static String secureHash(byte[] input) { 979 if (sMessageDigest != null) { 980 sMessageDigest.reset(); 981 sMessageDigest.update(input); 982 byte[] result = sMessageDigest.digest(); 983 return encodeHex(result); 984 } else { 985 return "Uninitialized SHA1"; 986 } 987 } 988 989 private static String encodeHex(byte[] bytes) { 990 StringBuffer hex = new StringBuffer(bytes.length * 2); 991 992 for (int i = 0; i < bytes.length; i++) { 993 int byteIntValue = bytes[i] & 0xff; 994 if (byteIntValue < 0x10) { 995 hex.append("0"); 996 } 997 hex.append(Integer.toString(byteIntValue, 16)); 998 } 999 1000 return hex.toString(); 1001 } 1002 1003 private static String getPrefixFromObject(Object obj) { 1004 return obj == null ? "<null>" : obj.getClass().getSimpleName(); 1005 } 1006 1007 private static String buildMessage(String prefix, String format, Object... args) { 1008 if (LOG_DBG) { 1009 checkIsThreadLogged(); 1010 } 1011 // Incorporate thread ID and calling method into prefix 1012 String sessionPostfix = ""; 1013 Session currentSession = sSessionMapper.get(getCallingThreadId()); 1014 if (currentSession != null) { 1015 sessionPostfix = ": " + currentSession.toString(); 1016 } 1017 1018 String msg; 1019 try { 1020 msg = (args == null || args.length == 0) ? format 1021 : String.format(Locale.US, format, args); 1022 } catch (IllegalFormatException ife) { 1023 e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format, 1024 args.length); 1025 msg = format + " (An error occurred while formatting the message.)"; 1026 } 1027 return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix); 1028 } 1029 } 1030