• 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 
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