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