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