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