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