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
17package android.telecom;
18
19import android.content.Context;
20import android.net.Uri;
21import android.os.AsyncTask;
22import android.os.Build;
23import android.telecom.Logging.EventManager;
24import android.telecom.Logging.Session;
25import android.telecom.Logging.SessionManager;
26import android.telephony.PhoneNumberUtils;
27import android.text.TextUtils;
28
29import com.android.internal.annotations.VisibleForTesting;
30import com.android.internal.util.IndentingPrintWriter;
31
32import java.security.MessageDigest;
33import java.security.NoSuchAlgorithmException;
34import java.util.IllegalFormatException;
35import java.util.Locale;
36
37/**
38 * Manages logging for the entire module.
39 *
40 * @hide
41 */
42public 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
77    private Log() {
78    }
79
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
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
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
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
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
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
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
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
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
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
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
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
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
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
181    public static void setSessionContext(Context context) {
182        getSessionManager().setContext(context);
183    }
184
185    public static void startSession(String shortMethodName) {
186        getSessionManager().startSession(shortMethodName, null);
187    }
188
189    public static void startSession(Session.Info info, String shortMethodName) {
190        getSessionManager().startSession(info, shortMethodName, null);
191    }
192
193    public static void startSession(String shortMethodName, String callerIdentification) {
194        getSessionManager().startSession(shortMethodName, callerIdentification);
195    }
196
197    public static void startSession(Session.Info info, String shortMethodName,
198            String callerIdentification) {
199        getSessionManager().startSession(info, shortMethodName, callerIdentification);
200    }
201
202    public static Session createSubsession() {
203        return getSessionManager().createSubsession();
204    }
205
206    public static Session.Info getExternalSession() {
207        return getSessionManager().getExternalSession();
208    }
209
210    public static void cancelSubsession(Session subsession) {
211        getSessionManager().cancelSubsession(subsession);
212    }
213
214    public static void continueSession(Session subsession, String shortMethodName) {
215        getSessionManager().continueSession(subsession, shortMethodName);
216    }
217
218    public static void endSession() {
219        getSessionManager().endSession();
220    }
221
222    public static void registerSessionListener(SessionManager.ISessionListener l) {
223        getSessionManager().registerSessionListener(l);
224    }
225
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
238    public static void addEvent(EventManager.Loggable recordEntry, String event) {
239        getEventManager().event(recordEntry, event, null);
240    }
241
242    public static void addEvent(EventManager.Loggable recordEntry, String event, Object data) {
243        getEventManager().event(recordEntry, event, data);
244    }
245
246    public static void addEvent(EventManager.Loggable recordEntry, String event, String format,
247            Object... args) {
248        getEventManager().event(recordEntry, event, format, args);
249    }
250
251    public static void registerEventListener(EventManager.EventListener e) {
252        getEventManager().registerEventListener(e);
253    }
254
255    public static void addRequestResponsePair(EventManager.TimedEventPair p) {
256        getEventManager().addRequestResponsePair(p);
257    }
258
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     */
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
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
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
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
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     */
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
367    public static boolean isLoggable(int level) {
368        return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
369    }
370
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     */
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
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
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
450    private static String getPrefixFromObject(Object obj) {
451        return obj == null ? "<null>" : obj.getClass().getSimpleName();
452    }
453
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