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 com.android.server.telecom;
18
19import android.net.Uri;
20import android.telecom.PhoneAccount;
21import android.telephony.PhoneNumberUtils;
22
23import java.security.MessageDigest;
24import java.security.NoSuchAlgorithmException;
25import java.util.IllegalFormatException;
26import java.util.Locale;
27
28/**
29 * Manages logging for the entire module.
30 */
31public class Log {
32
33    // Generic tag for all In Call logging
34    private static final String TAG = "Telecom";
35
36    public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
37    public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
38    public static final boolean INFO = isLoggable(android.util.Log.INFO);
39    public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
40    public static final boolean WARN = isLoggable(android.util.Log.WARN);
41    public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
42
43    private Log() {}
44
45    public static boolean isLoggable(int level) {
46        return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
47    }
48
49    public static void d(String prefix, String format, Object... args) {
50        if (DEBUG) {
51            android.util.Slog.d(TAG, buildMessage(prefix, format, args));
52        }
53    }
54
55    public static void d(Object objectPrefix, String format, Object... args) {
56        if (DEBUG) {
57            android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
58        }
59    }
60
61    public static void i(String prefix, String format, Object... args) {
62        if (INFO) {
63            android.util.Slog.i(TAG, buildMessage(prefix, format, args));
64        }
65    }
66
67    public static void i(Object objectPrefix, String format, Object... args) {
68        if (INFO) {
69            android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
70        }
71    }
72
73    public static void v(String prefix, String format, Object... args) {
74        if (VERBOSE) {
75            android.util.Slog.v(TAG, buildMessage(prefix, format, args));
76        }
77    }
78
79    public static void v(Object objectPrefix, String format, Object... args) {
80        if (VERBOSE) {
81            android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
82        }
83    }
84
85    public static void w(String prefix, String format, Object... args) {
86        if (WARN) {
87            android.util.Slog.w(TAG, buildMessage(prefix, format, args));
88        }
89    }
90
91    public static void w(Object objectPrefix, String format, Object... args) {
92        if (WARN) {
93            android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
94        }
95    }
96
97    public static void e(String prefix, Throwable tr, String format, Object... args) {
98        if (ERROR) {
99            android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr);
100        }
101    }
102
103    public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
104        if (ERROR) {
105            android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
106                    tr);
107        }
108    }
109
110    public static void wtf(String prefix, Throwable tr, String format, Object... args) {
111        android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr);
112    }
113
114    public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
115        android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
116                tr);
117    }
118
119    public static void wtf(String prefix, String format, Object... args) {
120        String msg = buildMessage(prefix, format, args);
121        android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
122    }
123
124    public static void wtf(Object objectPrefix, String format, Object... args) {
125        String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
126        android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
127    }
128
129    public static String piiHandle(Object pii) {
130        if (pii == null || VERBOSE) {
131            return String.valueOf(pii);
132        }
133
134        if (pii instanceof Uri) {
135            Uri uri = (Uri) pii;
136
137            // All Uri's which are not "tel" go through normal pii() method.
138            if (!PhoneAccount.SCHEME_TEL.equals(uri.getScheme())) {
139                return pii(pii);
140            } else {
141                pii = uri.getSchemeSpecificPart();
142            }
143        }
144
145        String originalString = String.valueOf(pii);
146        StringBuilder stringBuilder = new StringBuilder(originalString.length());
147        for (char c : originalString.toCharArray()) {
148            if (PhoneNumberUtils.isDialable(c)) {
149                stringBuilder.append('*');
150            } else {
151                stringBuilder.append(c);
152            }
153        }
154        return stringBuilder.toString();
155    }
156
157    /**
158     * Redact personally identifiable information for production users.
159     * If we are running in verbose mode, return the original string, otherwise
160     * return a SHA-1 hash of the input string.
161     */
162    public static String pii(Object pii) {
163        if (pii == null || VERBOSE) {
164            return String.valueOf(pii);
165        }
166        return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
167    }
168
169    private static String secureHash(byte[] input) {
170        MessageDigest messageDigest;
171        try {
172            messageDigest = MessageDigest.getInstance("SHA-1");
173        } catch (NoSuchAlgorithmException e) {
174            return null;
175        }
176        messageDigest.update(input);
177        byte[] result = messageDigest.digest();
178        return encodeHex(result);
179    }
180
181    private static String encodeHex(byte[] bytes) {
182        StringBuffer hex = new StringBuffer(bytes.length * 2);
183
184        for (int i = 0; i < bytes.length; i++) {
185            int byteIntValue = bytes[i] & 0xff;
186            if (byteIntValue < 0x10) {
187                hex.append("0");
188            }
189            hex.append(Integer.toString(byteIntValue, 16));
190        }
191
192        return hex.toString();
193    }
194
195    private static String getPrefixFromObject(Object obj) {
196        return obj == null ? "<null>" : obj.getClass().getSimpleName();
197    }
198
199    private static String buildMessage(String prefix, String format, Object... args) {
200        String msg;
201        try {
202            msg = (args == null || args.length == 0) ? format
203                    : String.format(Locale.US, format, args);
204        } catch (IllegalFormatException ife) {
205            e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
206                    args.length);
207            msg = format + " (An error occurred while formatting the message.)";
208        }
209        return String.format(Locale.US, "%s: %s", prefix, msg);
210    }
211}
212