• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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