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