blob: a90d0532b721032366f44856c5c3a041c7a2c4f4 [file] [log] [blame]
Ihab Awad60ac30b2014-05-20 22:32:12 -07001/*
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
Tyler Gunnef9f6f92014-09-12 22:16:17 -070017package android.telecom;
Ihab Awad60ac30b2014-05-20 22:32:12 -070018
Tyler Gunnffbcd892020-05-04 15:01:59 -070019import android.annotation.NonNull;
Artur Satayev2ebb31c2020-01-08 12:24:36 +000020import android.compat.annotation.UnsupportedAppUsage;
Tyler Gunnffbcd892020-05-04 15:01:59 -070021import android.content.ComponentName;
Brad Ebinger4fb372f2016-10-05 15:47:28 -070022import android.content.Context;
Santos Cordon3c20d632016-02-25 16:12:35 -080023import android.net.Uri;
youhei.x.miyoshib3cd7b52016-12-12 21:10:54 +090024import android.os.Build;
Brad Ebinger51b98342016-09-22 16:30:46 -070025import android.telecom.Logging.EventManager;
26import android.telecom.Logging.Session;
27import android.telecom.Logging.SessionManager;
Santos Cordon3c20d632016-02-25 16:12:35 -080028import android.telephony.PhoneNumberUtils;
29import android.text.TextUtils;
Hall Liue362e502016-01-07 17:35:54 -080030
Brad Ebinger51b98342016-09-22 16:30:46 -070031import com.android.internal.annotations.VisibleForTesting;
32import com.android.internal.util.IndentingPrintWriter;
33
Tyler Gunnffbcd892020-05-04 15:01:59 -070034import java.util.Arrays;
Ihab Awad60ac30b2014-05-20 22:32:12 -070035import java.util.IllegalFormatException;
36import java.util.Locale;
Tyler Gunnffbcd892020-05-04 15:01:59 -070037import java.util.stream.Collectors;
Ihab Awad60ac30b2014-05-20 22:32:12 -070038
39/**
40 * Manages logging for the entire module.
41 *
42 * @hide
43 */
Brad Ebinger51b98342016-09-22 16:30:46 -070044public class Log {
Ihab Awad60ac30b2014-05-20 22:32:12 -070045
Brad Ebinger51b98342016-09-22 16:30:46 -070046 private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
Ihab Awad60ac30b2014-05-20 22:32:12 -070047
Brad Ebinger51b98342016-09-22 16:30:46 -070048 private static final int EVENTS_TO_CACHE = 10;
49 private static final int EVENTS_TO_CACHE_DEBUG = 20;
50
Tyler Gunn9bc35112018-04-23 09:52:25 -070051 /**
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
Brad Ebinger51b98342016-09-22 16:30:46 -070056 // Generic tag for all Telecom logging
57 @VisibleForTesting
58 public static String TAG = "TelecomFramework";
Brad Ebinger0c3541b2016-11-01 14:11:38 -070059 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);
Brad Ebinger51b98342016-09-22 16:30:46 -070064
65 private static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
Jeff Sharkey5ab02432017-06-27 11:01:36 -060066 private static final boolean USER_BUILD = Build.IS_USER;
Ihab Awad60ac30b2014-05-20 22:32:12 -070067
Brad Ebinger51b98342016-09-22 16:30:46 -070068 // 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
84 private Log() {
85 }
86
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
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
Andrei Oneafbc8cfd2019-03-22 11:32:59 +0000105 @UnsupportedAppUsage
Brad Ebinger51b98342016-09-22 16:30:46 -0700106 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
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
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
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
Andrei Oneafbc8cfd2019-03-22 11:32:59 +0000136 @UnsupportedAppUsage
Brad Ebinger51b98342016-09-22 16:30:46 -0700137 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
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
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
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
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
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
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
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
Brad Ebinger4fb372f2016-10-05 15:47:28 -0700190 public static void setSessionContext(Context context) {
191 getSessionManager().setContext(context);
192 }
193
Brad Ebinger51b98342016-09-22 16:30:46 -0700194 public static void startSession(String shortMethodName) {
195 getSessionManager().startSession(shortMethodName, null);
196 }
197
Brad Ebinger3445f822016-10-24 16:40:49 -0700198 public static void startSession(Session.Info info, String shortMethodName) {
199 getSessionManager().startSession(info, shortMethodName, null);
200 }
201
Brad Ebinger51b98342016-09-22 16:30:46 -0700202 public static void startSession(String shortMethodName, String callerIdentification) {
203 getSessionManager().startSession(shortMethodName, callerIdentification);
204 }
205
Brad Ebingera0dc9762016-10-21 09:41:29 -0700206 public static void startSession(Session.Info info, String shortMethodName,
207 String callerIdentification) {
208 getSessionManager().startSession(info, shortMethodName, callerIdentification);
209 }
210
Brad Ebinger51b98342016-09-22 16:30:46 -0700211 public static Session createSubsession() {
212 return getSessionManager().createSubsession();
213 }
214
Brad Ebinger3445f822016-10-24 16:40:49 -0700215 public static Session.Info getExternalSession() {
216 return getSessionManager().getExternalSession();
217 }
218
Tyler Gunnffbcd892020-05-04 15:01:59 -0700219 /**
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 */
225 public static Session.Info getExternalSession(@NonNull String ownerInfo) {
226 return getSessionManager().getExternalSession(ownerInfo);
227 }
228
Brad Ebinger51b98342016-09-22 16:30:46 -0700229 public static void cancelSubsession(Session subsession) {
230 getSessionManager().cancelSubsession(subsession);
231 }
232
233 public static void continueSession(Session subsession, String shortMethodName) {
234 getSessionManager().continueSession(subsession, shortMethodName);
235 }
236
237 public static void endSession() {
238 getSessionManager().endSession();
239 }
240
Brad Ebinger836efad2016-10-18 13:48:17 -0700241 public static void registerSessionListener(SessionManager.ISessionListener l) {
242 getSessionManager().registerSessionListener(l);
243 }
244
Brad Ebinger51b98342016-09-22 16:30:46 -0700245 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
257 public static void addEvent(EventManager.Loggable recordEntry, String event) {
258 getEventManager().event(recordEntry, event, null);
259 }
260
261 public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) {
262 getEventManager().event(recordEntry, event, data);
263 }
264
265 public static void addEvent(EventManager.Loggable recordEntry, String event, String format,
266 Object... args) {
267 getEventManager().event(recordEntry, event, format, args);
268 }
269
270 public static void registerEventListener(EventManager.EventListener e) {
271 getEventManager().registerEventListener(e);
272 }
273
274 public static void addRequestResponsePair(EventManager.TimedEventPair p) {
275 getEventManager().addRequestResponsePair(p);
276 }
277
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 /**
Tyler Gunn2db81b52017-05-19 10:10:23 -0700291 * Dumps the events in a timeline format.
292 * @param pw The {@link IndentingPrintWriter} to write to.
293 * @hide
294 */
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 /**
Brad Ebinger51b98342016-09-22 16:30:46 -0700308 * 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 */
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
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
Brad Ebinger8adafe72017-06-08 15:44:40 -0700347 @VisibleForTesting
348 public static SessionManager getSessionManager() {
Brad Ebinger51b98342016-09-22 16:30:46 -0700349 // 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
Brad Ebinger51b98342016-09-22 16:30:46 -0700362 public static void setTag(String tag) {
363 TAG = tag;
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700364 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);
Brad Ebinger51b98342016-09-22 16:30:46 -0700369 }
370
371 /**
372 * If user enabled extended logging is enabled and the time limit has passed, disables the
373 * extended logging.
374 */
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
Ihab Awad60ac30b2014-05-20 22:32:12 -0700386 public static boolean isLoggable(int level) {
387 return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
388 }
389
Tyler Gunnfd74d53f2019-02-13 13:15:56 -0800390 /**
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 */
Brad Ebinger51b98342016-09-22 16:30:46 -0700396 public static String piiHandle(Object pii) {
397 if (pii == null || VERBOSE) {
398 return String.valueOf(pii);
Ihab Awad60ac30b2014-05-20 22:32:12 -0700399 }
Ihab Awad60ac30b2014-05-20 22:32:12 -0700400
Brad Ebinger51b98342016-09-22 16:30:46 -0700401 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)) {
Tyler Gunnfd74d53f2019-02-13 13:15:56 -0800412 obfuscatePhoneNumber(sb, textToObfuscate);
Brad Ebinger51b98342016-09-22 16:30:46 -0700413 } 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 }
Tyler Gunnfd74d53f2019-02-13 13:15:56 -0800424 } else if (pii instanceof String) {
425 String number = (String) pii;
426 obfuscatePhoneNumber(sb, number);
Ihab Awad60ac30b2014-05-20 22:32:12 -0700427 }
Ihab Awad60ac30b2014-05-20 22:32:12 -0700428
Brad Ebinger51b98342016-09-22 16:30:46 -0700429 return sb.toString();
Ihab Awad60ac30b2014-05-20 22:32:12 -0700430 }
431
432 /**
Tyler Gunnfd74d53f2019-02-13 13:15:56 -0800433 * 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 */
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 /**
Tyler Gunn9bc35112018-04-23 09:52:25 -0700452 * 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 */
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 /**
Ihab Awad60ac30b2014-05-20 22:32:12 -0700467 * Redact personally identifiable information for production users.
youhei.x.miyoshib3cd7b52016-12-12 21:10:54 +0900468 * If we are running in verbose mode, return the original string,
Brad Ebingerf784b292017-12-22 13:45:27 -0800469 * and return "***" otherwise.
Ihab Awad60ac30b2014-05-20 22:32:12 -0700470 */
471 public static String pii(Object pii) {
472 if (pii == null || VERBOSE) {
473 return String.valueOf(pii);
474 }
Brad Ebingerf784b292017-12-22 13:45:27 -0800475 return "***";
Ihab Awad60ac30b2014-05-20 22:32:12 -0700476 }
477
478 private static String getPrefixFromObject(Object obj) {
479 return obj == null ? "<null>" : obj.getClass().getSimpleName();
480 }
481
482 private static String buildMessage(String prefix, String format, Object... args) {
Brad Ebinger51b98342016-09-22 16:30:46 -0700483 // Incorporate thread ID and calling method into prefix
484 String sessionName = getSessionId();
485 String sessionPostfix = TextUtils.isEmpty(sessionName) ? "" : ": " + sessionName;
486
Ihab Awad60ac30b2014-05-20 22:32:12 -0700487 String msg;
488 try {
489 msg = (args == null || args.length == 0) ? format
490 : String.format(Locale.US, format, args);
491 } catch (IllegalFormatException ife) {
Brad Ebinger4fb372f2016-10-05 15:47:28 -0700492 e(TAG, ife, "Log: IllegalFormatException: formatString='%s' numArgs=%d", format,
Ihab Awad60ac30b2014-05-20 22:32:12 -0700493 args.length);
494 msg = format + " (An error occurred while formatting the message.)";
495 }
Brad Ebinger51b98342016-09-22 16:30:46 -0700496 return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
Ihab Awad60ac30b2014-05-20 22:32:12 -0700497 }
Tyler Gunnffbcd892020-05-04 15:01:59 -0700498
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 */
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 */
520 public static String getPackageAbbreviation(String packageName) {
521 if (packageName == null) {
522 return "";
523 }
524 return Arrays.stream(packageName.split("\\."))
525 .map(s -> s.substring(0,1))
526 .collect(Collectors.joining(""));
527 }
Ihab Awad60ac30b2014-05-20 22:32:12 -0700528}