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 android.telecom; 18 19 import android.annotation.NonNull; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.net.Uri; 24 import android.os.Build; 25 import android.telecom.Logging.EventManager; 26 import android.telecom.Logging.Session; 27 import android.telecom.Logging.SessionManager; 28 import android.telephony.PhoneNumberUtils; 29 import android.text.TextUtils; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.util.IndentingPrintWriter; 33 34 import java.util.Arrays; 35 import java.util.IllegalFormatException; 36 import java.util.Locale; 37 import java.util.stream.Collectors; 38 39 /** 40 * Manages logging for the entire module. 41 * 42 * @hide 43 */ 44 public class Log { 45 46 private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes 47 48 private static final int EVENTS_TO_CACHE = 10; 49 private static final int EVENTS_TO_CACHE_DEBUG = 20; 50 51 /** 52 * When generating a bug report, include the last X dialable digits when logging phone numbers. 53 */ 54 private static final int NUM_DIALABLE_DIGITS_TO_LOG = Build.IS_USER ? 0 : 2; 55 56 // Generic tag for all Telecom logging 57 @VisibleForTesting 58 public static String TAG = "TelecomFramework"; 59 public static boolean DEBUG = isLoggable(android.util.Log.DEBUG); 60 public static boolean INFO = isLoggable(android.util.Log.INFO); 61 public static boolean VERBOSE = isLoggable(android.util.Log.VERBOSE); 62 public static boolean WARN = isLoggable(android.util.Log.WARN); 63 public static boolean ERROR = isLoggable(android.util.Log.ERROR); 64 65 private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */ 66 private static final boolean USER_BUILD = Build.IS_USER; 67 68 // Used to synchronize singleton logging lazy initialization 69 private static final Object sSingletonSync = new Object(); 70 private static EventManager sEventManager; 71 private static SessionManager sSessionManager; 72 private static Object sLock = null; 73 74 /** 75 * Tracks whether user-activated extended logging is enabled. 76 */ 77 private static boolean sIsUserExtendedLoggingEnabled = false; 78 79 /** 80 * Enabled in telecom testing to help gate log statements causing log spew. 81 */ 82 private static boolean sIsUnitTestingEnabled = false; 83 84 /** 85 * The time when user-activated extended logging should be ended. Used to determine when 86 * extended logging should automatically be disabled. 87 */ 88 private static long sUserExtendedLoggingStopTime = 0; 89 Log()90 private Log() { 91 } 92 d(String prefix, String format, Object... args)93 public static void d(String prefix, String format, Object... args) { 94 if (sIsUserExtendedLoggingEnabled) { 95 maybeDisableLogging(); 96 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 97 } else if (DEBUG) { 98 android.util.Slog.d(TAG, buildMessage(prefix, format, args)); 99 } 100 } 101 d(Object objectPrefix, String format, Object... args)102 public static void d(Object objectPrefix, String format, Object... args) { 103 if (sIsUserExtendedLoggingEnabled) { 104 maybeDisableLogging(); 105 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 106 } else if (DEBUG) { 107 android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 108 } 109 } 110 111 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) i(String prefix, String format, Object... args)112 public static void i(String prefix, String format, Object... args) { 113 if (INFO) { 114 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 115 } 116 } 117 i(Object objectPrefix, String format, Object... args)118 public static void i(Object objectPrefix, String format, Object... args) { 119 if (INFO) { 120 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 121 } 122 } 123 v(String prefix, String format, Object... args)124 public static void v(String prefix, String format, Object... args) { 125 if (sIsUserExtendedLoggingEnabled) { 126 maybeDisableLogging(); 127 android.util.Slog.i(TAG, buildMessage(prefix, format, args)); 128 } else if (VERBOSE) { 129 android.util.Slog.v(TAG, buildMessage(prefix, format, args)); 130 } 131 } 132 v(Object objectPrefix, String format, Object... args)133 public static void v(Object objectPrefix, String format, Object... args) { 134 if (sIsUserExtendedLoggingEnabled) { 135 maybeDisableLogging(); 136 android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 137 } else if (VERBOSE) { 138 android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 139 } 140 } 141 142 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) w(String prefix, String format, Object... args)143 public static void w(String prefix, String format, Object... args) { 144 if (WARN) { 145 android.util.Slog.w(TAG, buildMessage(prefix, format, args)); 146 } 147 } 148 w(Object objectPrefix, String format, Object... args)149 public static void w(Object objectPrefix, String format, Object... args) { 150 if (WARN) { 151 android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args)); 152 } 153 } 154 e(String prefix, Throwable tr, String format, Object... args)155 public static void e(String prefix, Throwable tr, String format, Object... args) { 156 if (ERROR) { 157 android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr); 158 } 159 } 160 e(Object objectPrefix, Throwable tr, String format, Object... args)161 public static void e(Object objectPrefix, Throwable tr, String format, Object... args) { 162 if (ERROR) { 163 android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 164 tr); 165 } 166 } 167 wtf(String prefix, Throwable tr, String format, Object... args)168 public static void wtf(String prefix, Throwable tr, String format, Object... args) { 169 android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr); 170 } 171 wtf(Object objectPrefix, Throwable tr, String format, Object... args)172 public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) { 173 android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args), 174 tr); 175 } 176 wtf(String prefix, String format, Object... args)177 public static void wtf(String prefix, String format, Object... args) { 178 String msg = buildMessage(prefix, format, args); 179 android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg)); 180 } 181 wtf(Object objectPrefix, String format, Object... args)182 public static void wtf(Object objectPrefix, String format, Object... args) { 183 String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args); 184 android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg)); 185 } 186 187 /** 188 * The ease of use methods below only act mostly as proxies to the Session and Event Loggers. 189 * They also control the lazy loaders of the singleton instances, which will never be loaded if 190 * the proxy methods aren't used. 191 * 192 * Please see each method's documentation inside of their respective implementations in the 193 * loggers. 194 */ 195 setSessionContext(Context context)196 public static void setSessionContext(Context context) { 197 getSessionManager().setContext(context); 198 } 199 startSession(String shortMethodName)200 public static void startSession(String shortMethodName) { 201 getSessionManager().startSession(shortMethodName, null); 202 } 203 startSession(Session.Info info, String shortMethodName)204 public static void startSession(Session.Info info, String shortMethodName) { 205 getSessionManager().startSession(info, shortMethodName, null); 206 } 207 startSession(String shortMethodName, String callerIdentification)208 public static void startSession(String shortMethodName, String callerIdentification) { 209 getSessionManager().startSession(shortMethodName, callerIdentification); 210 } 211 startSession(Session.Info info, String shortMethodName, String callerIdentification)212 public static void startSession(Session.Info info, String shortMethodName, 213 String callerIdentification) { 214 getSessionManager().startSession(info, shortMethodName, callerIdentification); 215 } 216 createSubsession()217 public static Session createSubsession() { 218 return getSessionManager().createSubsession(); 219 } 220 getExternalSession()221 public static Session.Info getExternalSession() { 222 return getSessionManager().getExternalSession(); 223 } 224 225 /** 226 * Retrieves external session information, providing a context for the recipient of the session 227 * info where the external session came from. 228 * @param ownerInfo The external owner info. 229 * @return New {@link Session.Info} instance with owner info set. 230 */ getExternalSession(@onNull String ownerInfo)231 public static Session.Info getExternalSession(@NonNull String ownerInfo) { 232 return getSessionManager().getExternalSession(ownerInfo); 233 } 234 cancelSubsession(Session subsession)235 public static void cancelSubsession(Session subsession) { 236 getSessionManager().cancelSubsession(subsession); 237 } 238 continueSession(Session subsession, String shortMethodName)239 public static void continueSession(Session subsession, String shortMethodName) { 240 getSessionManager().continueSession(subsession, shortMethodName); 241 } 242 endSession()243 public static void endSession() { 244 getSessionManager().endSession(); 245 } 246 registerSessionListener(SessionManager.ISessionListener l)247 public static void registerSessionListener(SessionManager.ISessionListener l) { 248 getSessionManager().registerSessionListener(l); 249 } 250 getSessionId()251 public static String getSessionId() { 252 // If the Session logger has not been initialized, then there have been no sessions logged. 253 // Don't load it now! 254 synchronized (sSingletonSync) { 255 if (sSessionManager != null) { 256 return getSessionManager().getSessionId(); 257 } else { 258 return ""; 259 } 260 } 261 } 262 addEvent(EventManager.Loggable recordEntry, String event)263 public static void addEvent(EventManager.Loggable recordEntry, String event) { 264 getEventManager().event(recordEntry, event, null); 265 } 266 addEvent(EventManager.Loggable recordEntry, String event, Object data)267 public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) { 268 getEventManager().event(recordEntry, event, data); 269 } 270 addEvent(EventManager.Loggable recordEntry, String event, String format, Object... args)271 public static void addEvent(EventManager.Loggable recordEntry, String event, String format, 272 Object... args) { 273 getEventManager().event(recordEntry, event, format, args); 274 } 275 registerEventListener(EventManager.EventListener e)276 public static void registerEventListener(EventManager.EventListener e) { 277 getEventManager().registerEventListener(e); 278 } 279 addRequestResponsePair(EventManager.TimedEventPair p)280 public static void addRequestResponsePair(EventManager.TimedEventPair p) { 281 getEventManager().addRequestResponsePair(p); 282 } 283 dumpEvents(IndentingPrintWriter pw)284 public static void dumpEvents(IndentingPrintWriter pw) { 285 // If the Events logger has not been initialized, then there have been no events logged. 286 // Don't load it now! 287 synchronized (sSingletonSync) { 288 if (sEventManager != null) { 289 getEventManager().dumpEvents(pw); 290 } else { 291 pw.println("No Historical Events Logged."); 292 } 293 } 294 } 295 296 /** 297 * Dumps the events in a timeline format. 298 * @param pw The {@link IndentingPrintWriter} to write to. 299 * @hide 300 */ dumpEventsTimeline(IndentingPrintWriter pw)301 public static void dumpEventsTimeline(IndentingPrintWriter pw) { 302 // If the Events logger has not been initialized, then there have been no events logged. 303 // Don't load it now! 304 synchronized (sSingletonSync) { 305 if (sEventManager != null) { 306 getEventManager().dumpEventsTimeline(pw); 307 } else { 308 pw.println("No Historical Events Logged."); 309 } 310 } 311 } 312 313 /** 314 * Enable or disable extended telecom logging. 315 * 316 * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled, 317 * {@code false} if it should be disabled. 318 */ setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled)319 public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) { 320 // If the state hasn't changed, bail early. 321 if (sIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) { 322 return; 323 } 324 325 if (sEventManager != null) { 326 sEventManager.changeEventCacheSize(isExtendedLoggingEnabled ? 327 EVENTS_TO_CACHE_DEBUG : EVENTS_TO_CACHE); 328 } 329 330 sIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled; 331 if (sIsUserExtendedLoggingEnabled) { 332 sUserExtendedLoggingStopTime = System.currentTimeMillis() 333 + EXTENDED_LOGGING_DURATION_MILLIS; 334 } else { 335 sUserExtendedLoggingStopTime = 0; 336 } 337 } 338 339 /** 340 * Enabled when tests are running to help gate log statements causing log spew. 341 * 342 * @param isEnabled {@code true} if running unit tests. false otherwise. 343 * 344 */ setUnitTestingEnabled(boolean isEnabled)345 public static void setUnitTestingEnabled(boolean isEnabled) { 346 sIsUnitTestingEnabled = isEnabled; 347 } 348 isUnitTestingEnabled()349 public static boolean isUnitTestingEnabled() { 350 return sIsUnitTestingEnabled; 351 } 352 getEventManager()353 private static EventManager getEventManager() { 354 // Checking for null again outside of synchronization because we only need to synchronize 355 // during the lazy loading of the events logger. We don't need to synchronize elsewhere. 356 if (sEventManager == null) { 357 synchronized (sSingletonSync) { 358 if (sEventManager == null) { 359 sEventManager = new EventManager(Log::getSessionId); 360 return sEventManager; 361 } 362 } 363 } 364 return sEventManager; 365 } 366 367 @VisibleForTesting getSessionManager()368 public static SessionManager getSessionManager() { 369 // Checking for null again outside of synchronization because we only need to synchronize 370 // during the lazy loading of the session logger. We don't need to synchronize elsewhere. 371 if (sSessionManager == null) { 372 synchronized (sSingletonSync) { 373 if (sSessionManager == null) { 374 sSessionManager = new SessionManager(); 375 return sSessionManager; 376 } 377 } 378 } 379 return sSessionManager; 380 } 381 setTag(String tag)382 public static void setTag(String tag) { 383 TAG = tag; 384 DEBUG = isLoggable(android.util.Log.DEBUG); 385 INFO = isLoggable(android.util.Log.INFO); 386 VERBOSE = isLoggable(android.util.Log.VERBOSE); 387 WARN = isLoggable(android.util.Log.WARN); 388 ERROR = isLoggable(android.util.Log.ERROR); 389 } 390 391 /** 392 * Sets the main telecom sync lock used within Telecom. This is used when building log messages 393 * so that we can identify places in the code where we are doing something outside of the 394 * Telecom lock. 395 * @param lock The lock. 396 */ setLock(Object lock)397 public static void setLock(Object lock) { 398 // Don't do lock monitoring on user builds. 399 if (!Build.IS_USER) { 400 sLock = lock; 401 } 402 } 403 404 /** 405 * If user enabled extended logging is enabled and the time limit has passed, disables the 406 * extended logging. 407 */ maybeDisableLogging()408 private static void maybeDisableLogging() { 409 if (!sIsUserExtendedLoggingEnabled) { 410 return; 411 } 412 413 if (sUserExtendedLoggingStopTime < System.currentTimeMillis()) { 414 sUserExtendedLoggingStopTime = 0; 415 sIsUserExtendedLoggingEnabled = false; 416 } 417 } 418 isLoggable(int level)419 public static boolean isLoggable(int level) { 420 return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level); 421 } 422 423 /** 424 * Generates an obfuscated string for a calling handle in {@link Uri} format, or a raw phone 425 * phone number in {@link String} format. 426 * @param pii The information to obfuscate. 427 * @return The obfuscated string. 428 */ piiHandle(Object pii)429 public static String piiHandle(Object pii) { 430 if (pii == null || VERBOSE) { 431 return String.valueOf(pii); 432 } 433 434 StringBuilder sb = new StringBuilder(); 435 if (pii instanceof Uri) { 436 Uri uri = (Uri) pii; 437 String scheme = uri.getScheme(); 438 439 if (!TextUtils.isEmpty(scheme)) { 440 sb.append(scheme).append(":"); 441 } 442 443 String textToObfuscate = uri.getSchemeSpecificPart(); 444 if (PhoneAccount.SCHEME_TEL.equals(scheme)) { 445 obfuscatePhoneNumber(sb, textToObfuscate); 446 } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 447 for (int i = 0; i < textToObfuscate.length(); i++) { 448 char c = textToObfuscate.charAt(i); 449 if (c != '@' && c != '.') { 450 c = '*'; 451 } 452 sb.append(c); 453 } 454 } else { 455 sb.append(pii(pii)); 456 } 457 } else if (pii instanceof String) { 458 String number = (String) pii; 459 obfuscatePhoneNumber(sb, number); 460 } 461 462 return sb.toString(); 463 } 464 465 /** 466 * Obfuscates a phone number, allowing NUM_DIALABLE_DIGITS_TO_LOG digits to be exposed for the 467 * phone number. 468 * @param sb String buffer to write obfuscated number to. 469 * @param phoneNumber The number to obfuscate. 470 */ obfuscatePhoneNumber(StringBuilder sb, String phoneNumber)471 private static void obfuscatePhoneNumber(StringBuilder sb, String phoneNumber) { 472 int numDigitsToObfuscate = getDialableCount(phoneNumber) 473 - NUM_DIALABLE_DIGITS_TO_LOG; 474 for (int i = 0; i < phoneNumber.length(); i++) { 475 char c = phoneNumber.charAt(i); 476 boolean isDialable = PhoneNumberUtils.isDialable(c); 477 if (isDialable) { 478 numDigitsToObfuscate--; 479 } 480 sb.append(isDialable && numDigitsToObfuscate >= 0 ? "*" : c); 481 } 482 } 483 484 /** 485 * Determines the number of dialable characters in a string. 486 * @param toCount The string to count dialable characters in. 487 * @return The count of dialable characters. 488 */ getDialableCount(String toCount)489 private static int getDialableCount(String toCount) { 490 int numDialable = 0; 491 for (char c : toCount.toCharArray()) { 492 if (PhoneNumberUtils.isDialable(c)) { 493 numDialable++; 494 } 495 } 496 return numDialable; 497 } 498 499 /** 500 * Redact personally identifiable information for production users. 501 * If we are running in verbose mode, return the original string, 502 * and return "***" otherwise. 503 */ pii(Object pii)504 public static String pii(Object pii) { 505 if (pii == null || VERBOSE) { 506 return String.valueOf(pii); 507 } 508 return "***"; 509 } 510 getPrefixFromObject(Object obj)511 private static String getPrefixFromObject(Object obj) { 512 return obj == null ? "<null>" : obj.getClass().getSimpleName(); 513 } 514 buildMessage(String prefix, String format, Object... args)515 private static String buildMessage(String prefix, String format, Object... args) { 516 // Incorporate thread ID and calling method into prefix 517 String sessionName = getSessionId(); 518 String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName; 519 520 String msg; 521 try { 522 msg = (args == null || args.length == 0) ? format 523 : String.format(Locale.US, format, args); 524 } catch (IllegalFormatException ife) { 525 e(TAG, ife, "Log: IllegalFormatException: formatString='%s' numArgs=%d", format, 526 args.length); 527 msg = format + " (An error occurred while formatting the message.)"; 528 } 529 // If a lock was set, check if this thread holds that lock and output an emoji that lets 530 // the developer know whether a log message came from within the Telecom lock or not. 531 String isLocked = sLock != null ? (Thread.holdsLock(sLock) ? "\uD83D\uDD12" : "❗") : ""; 532 return String.format(Locale.US, "%s: %s%s%s", prefix, msg, sessionPostfix, isLocked); 533 } 534 535 /** 536 * Generates an abbreviated version of the package name from a component. 537 * E.g. com.android.phone becomes cap 538 * @param componentName The component name to abbreviate. 539 * @return Abbreviation of empty string if component is null. 540 * @hide 541 */ getPackageAbbreviation(ComponentName componentName)542 public static String getPackageAbbreviation(ComponentName componentName) { 543 if (componentName == null) { 544 return ""; 545 } 546 return getPackageAbbreviation(componentName.getPackageName()); 547 } 548 549 /** 550 * Generates an abbreviated version of the package name. 551 * E.g. com.android.phone becomes cap 552 * @param packageName The packageName name to abbreviate. 553 * @return Abbreviation of empty string if package is null. 554 * @hide 555 */ getPackageAbbreviation(String packageName)556 public static String getPackageAbbreviation(String packageName) { 557 if (packageName == null) { 558 return ""; 559 } 560 return Arrays.stream(packageName.split("\\.")) 561 .map(s -> s.length() == 0 ? "" : s.substring(0, 1)) 562 .collect(Collectors.joining("")); 563 } 564 } 565