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 com.android.server.telecom;
18 
19 import android.content.Context;
20 import android.net.Uri;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.os.AsyncTask;
24 import android.telecom.PhoneAccount;
25 import android.telephony.PhoneNumberUtils;
26 import android.text.TextUtils;
27 import android.util.Base64;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.internal.util.IndentingPrintWriter;
31 
32 import java.nio.ByteBuffer;
33 import java.security.MessageDigest;
34 import java.security.NoSuchAlgorithmException;
35 import java.text.DateFormat;
36 import java.text.SimpleDateFormat;
37 import java.util.Arrays;
38 import java.util.Date;
39 import java.util.HashMap;
40 import java.util.IllegalFormatException;
41 import java.util.Iterator;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.concurrent.ConcurrentHashMap;
47 import java.util.concurrent.LinkedBlockingQueue;
48 
49 /**
50  * Manages logging for the entire module.
51  */
52 @VisibleForTesting
53 public class Log {
54 
55     /**
56      * Stores the various events associated with {@link Call}s. Also stores all request-response
57      * pairs amongst the events.
58      */
59     public final static class Events {
60         public static final String CREATED = "CREATED";
61         public static final String DESTROYED = "DESTROYED";
62         public static final String SET_NEW = "SET_NEW";
63         public static final String SET_CONNECTING = "SET_CONNECTING";
64         public static final String SET_DIALING = "SET_DIALING";
65         public static final String SET_ACTIVE = "SET_ACTIVE";
66         public static final String SET_HOLD = "SET_HOLD";
67         public static final String SET_RINGING = "SET_RINGING";
68         public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
69         public static final String SET_DISCONNECTING = "SET_DISCONNECTING";
70         public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
71         public static final String REQUEST_HOLD = "REQUEST_HOLD";
72         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
73         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
74         public static final String REQUEST_ACCEPT = "REQUEST_ACCEPT";
75         public static final String REQUEST_REJECT = "REQUEST_REJECT";
76         public static final String START_DTMF = "START_DTMF";
77         public static final String STOP_DTMF = "STOP_DTMF";
78         public static final String START_RINGER = "START_RINGER";
79         public static final String STOP_RINGER = "STOP_RINGER";
80         public static final String SKIP_RINGING = "SKIP_RINGING";
81         public static final String START_CALL_WAITING_TONE = "START_CALL_WAITING_TONE";
82         public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
83         public static final String START_CONNECTION = "START_CONNECTION";
84         public static final String BIND_CS = "BIND_CS";
85         public static final String CS_BOUND = "CS_BOUND";
86         public static final String CONFERENCE_WITH = "CONF_WITH";
87         public static final String SPLIT_CONFERENCE = "CONF_SPLIT";
88         public static final String SWAP = "SWAP";
89         public static final String ADD_CHILD = "ADD_CHILD";
90         public static final String REMOVE_CHILD = "REMOVE_CHILD";
91         public static final String SET_PARENT = "SET_PARENT";
92         public static final String MUTE = "MUTE";
93         public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
94         public static final String ERROR_LOG = "ERROR";
95         public static final String USER_LOG_MARK = "USER_LOG_MARK";
96         public static final String SILENCE = "SILENCE";
97         public static final String BIND_SCREENING = "BIND_SCREENING";
98         public static final String SCREENING_BOUND = "SCREENING_BOUND";
99         public static final String SCREENING_SENT = "SCREENING_SENT";
100         public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
101         public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
102         public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
103         public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED";
104         public static final String DIRECT_TO_VM_FINISHED = "DIRECT_TO_VM_FINISHED";
105         public static final String FILTERING_INITIATED = "FILTERING_INITIATED";
106         public static final String FILTERING_COMPLETED = "FILTERING_COMPLETED";
107         public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT";
108         public static final String REMOTELY_HELD = "REMOTELY_HELD";
109         public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
110         public static final String PULL = "PULL";
111         public static final String INFO = "INFO";
112 
113         /**
114          * Maps from a request to a response.  The same event could be listed as the
115          * response for multiple requests (e.g. REQUEST_ACCEPT and REQUEST_UNHOLD both map to the
116          * SET_ACTIVE response). This map is used to print out the amount of time it takes between
117          * a request and a response.
118          */
119         public static final Map<String, String> requestResponsePairs =
120                 new HashMap<String, String>() {{
121                     put(REQUEST_ACCEPT, SET_ACTIVE);
122                     put(REQUEST_REJECT, SET_DISCONNECTED);
123                     put(REQUEST_DISCONNECT, SET_DISCONNECTED);
124                     put(REQUEST_HOLD, SET_HOLD);
125                     put(REQUEST_UNHOLD, SET_ACTIVE);
126                     put(START_CONNECTION, SET_DIALING);
127                     put(BIND_CS, CS_BOUND);
128                     put(SCREENING_SENT, SCREENING_COMPLETED);
129                     put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_FINISHED);
130                     put(DIRECT_TO_VM_INITIATED, DIRECT_TO_VM_FINISHED);
131                     put(FILTERING_INITIATED, FILTERING_COMPLETED);
132                 }};
133     }
134 
135     public static class CallEvent {
136         public String eventId;
137         public String sessionId;
138         public long time;
139         public Object data;
140 
CallEvent(String eventId, String sessionId, long time, Object data)141         public CallEvent(String eventId, String sessionId, long time, Object data) {
142             this.eventId = eventId;
143             this.sessionId = sessionId;
144             this.time = time;
145             this.data = data;
146         }
147     }
148 
149     public static class CallEventRecord {
150         private static final DateFormat sLongDateFormat = new SimpleDateFormat(
151                 "yyyy-MM-dd HH:mm:ss.SSS");
152         private static final DateFormat sDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
153         private static int sNextId = 1;
154         private final List<CallEvent> mEvents = new LinkedList<>();
155         private final Call mCall;
156 
CallEventRecord(Call call)157         public CallEventRecord(Call call) {
158             mCall = call;
159         }
160 
getCall()161         public Call getCall() {
162             return mCall;
163         }
164 
addEvent(String event, String sessionId, Object data)165         public void addEvent(String event, String sessionId, Object data) {
166             mEvents.add(new CallEvent(event, sessionId, System.currentTimeMillis(), data));
167             Log.i("Event", "Call %s: %s, %s", mCall.getId(), event, data);
168         }
169 
dump(IndentingPrintWriter pw)170         public void dump(IndentingPrintWriter pw) {
171             Map<String, CallEvent> pendingResponses = new HashMap<>();
172 
173             pw.print("Call ");
174             pw.print(mCall.getId());
175             pw.print(" [");
176             pw.print(sLongDateFormat.format(new Date(mCall.getCreationTimeMillis())));
177             pw.print("]");
178             pw.println(mCall.isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
179 
180             pw.increaseIndent();
181             pw.println("To address: " + piiHandle(mCall.getHandle()));
182 
183             for (CallEvent event : mEvents) {
184 
185                 // We print out events in chronological order. During that process we look at each
186                 // event and see if it maps to a request on the Request-Response pairs map. If it
187                 // does, then we effectively start 'listening' for the response. We do that by
188                 // storing the response event ID in {@code pendingResponses}. When we find the
189                 // response in a later iteration of the loop, we grab the original request and
190                 // calculate the time it took to get a response.
191                 if (Events.requestResponsePairs.containsKey(event.eventId)) {
192                     // This event expects a response, so add that response to the maps
193                     // of pending events.
194                     String pendingResponse = Events.requestResponsePairs.get(event.eventId);
195                     pendingResponses.put(pendingResponse, event);
196                 }
197 
198                 pw.print(sDateFormat.format(new Date(event.time)));
199                 pw.print(" - ");
200                 pw.print(event.eventId);
201                 if (event.data != null) {
202                     pw.print(" (");
203                     Object data = event.data;
204 
205                     if (data instanceof Call) {
206                         // If the data is another call, then change the data to the call's CallEvent
207                         // ID instead.
208                         CallEventRecord record = mCallEventRecordMap.get(data);
209                         if (record != null) {
210                             data = "Call " + record.mCall.getId();
211                         }
212                     }
213 
214                     pw.print(data);
215                     pw.print(")");
216                 }
217 
218                 // If this event is a response event that we've been waiting for, calculate the time
219                 // it took for the response to complete and print that out as well.
220                 CallEvent requestEvent = pendingResponses.remove(event.eventId);
221                 if (requestEvent != null) {
222                     pw.print(", time since ");
223                     pw.print(requestEvent.eventId);
224                     pw.print(": ");
225                     pw.print(event.time - requestEvent.time);
226                     pw.print(" ms");
227                 }
228                 pw.print(":");
229                 pw.print(event.sessionId);
230                 pw.println();
231             }
232             pw.decreaseIndent();
233         }
234     }
235 
236     public static final int MAX_CALLS_TO_CACHE = 5;  // Arbitrarily chosen.
237     public static final int MAX_CALLS_TO_CACHE_DEBUG = 20;  // Arbitrarily chosen.
238     private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
239 
240     // Don't check in with this true!
241     private static final boolean LOG_DBG = false;
242 
243     // Currently using 3 letters, So don't exceed 64^3
244     private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144;
245 
246     // Generic tag for all In Call logging
247     @VisibleForTesting
248     public static String TAG = "Telecom";
249     public static String LOGGING_TAG = "Logging";
250 
251     public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
252     public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
253     public static final boolean DEBUG = isLoggable(android.util.Log.DEBUG);
254     public static final boolean INFO = isLoggable(android.util.Log.INFO);
255     public static final boolean VERBOSE = isLoggable(android.util.Log.VERBOSE);
256     public static final boolean WARN = isLoggable(android.util.Log.WARN);
257     public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
258 
259     private static final Map<Call, CallEventRecord> mCallEventRecordMap = new HashMap<>();
260     private static LinkedBlockingQueue<CallEventRecord> mCallEventRecords =
261             new LinkedBlockingQueue<CallEventRecord>(MAX_CALLS_TO_CACHE);
262 
263     private static Context mContext = null;
264     // Synchronized in all method calls
265     private static int sCodeEntryCounter = 0;
266     @VisibleForTesting
267     public static ConcurrentHashMap<Integer, Session> sSessionMapper = new ConcurrentHashMap<>(100);
268     @VisibleForTesting
269     public static Handler sSessionCleanupHandler = new Handler(Looper.getMainLooper());
270     @VisibleForTesting
271     public static java.lang.Runnable sCleanStaleSessions = new java.lang.Runnable() {
272         @Override
273         public void run() {
274             cleanupStaleSessions(getSessionCleanupTimeoutMs());
275         }
276     };
277 
278     // Set the logging container to be the system's. This will only change when being mocked
279     // during testing.
280     private static SystemLoggingContainer systemLogger = new SystemLoggingContainer();
281 
282     /**
283      * Tracks whether user-activated extended logging is enabled.
284      */
285     private static boolean mIsUserExtendedLoggingEnabled = false;
286 
287     /**
288      * The time when user-activated extended logging should be ended.  Used to determine when
289      * extended logging should automatically be disabled.
290      */
291     private static long mUserExtendedLoggingStopTime = 0;
292 
Log()293     private Log() {
294     }
295 
setContext(Context context)296     public static void setContext(Context context) {
297         mContext = context;
298     }
299 
300     /**
301      * Enable or disable extended telecom logging.
302      *
303      * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled,
304      *          {@code false} if it should be disabled.
305      */
setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled)306     public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) {
307         // If the state hasn't changed, bail early.
308         if (mIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) {
309             return;
310         }
311 
312         // Resize the event queue.
313         int newSize = isExtendedLoggingEnabled ? MAX_CALLS_TO_CACHE_DEBUG : MAX_CALLS_TO_CACHE;
314         LinkedBlockingQueue<CallEventRecord> oldEventLog = mCallEventRecords;
315         mCallEventRecords = new LinkedBlockingQueue<CallEventRecord>(newSize);
316         mCallEventRecordMap.clear();
317 
318         // Copy the existing queue into the new one.
319         for (CallEventRecord event : oldEventLog) {
320             addCallEventRecord(event);
321         }
322 
323         mIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled;
324         if (mIsUserExtendedLoggingEnabled) {
325             mUserExtendedLoggingStopTime = System.currentTimeMillis()
326                     + EXTENDED_LOGGING_DURATION_MILLIS;
327         } else {
328             mUserExtendedLoggingStopTime = 0;
329         }
330     }
331 
332     public static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
333     private static MessageDigest sMessageDigest;
334 
initMd5Sum()335     public static void initMd5Sum() {
336         new AsyncTask<Void, Void, Void>() {
337             @Override
338             public Void doInBackground(Void... args) {
339                 MessageDigest md;
340                 try {
341                     md = MessageDigest.getInstance("SHA-1");
342                 } catch (NoSuchAlgorithmException e) {
343                     md = null;
344                 }
345                 sMessageDigest = md;
346                 return null;
347             }
348         }.execute();
349     }
350 
351     @VisibleForTesting
setTag(String tag)352     public static void setTag(String tag) {
353         TAG = tag;
354     }
355 
356     @VisibleForTesting
setLoggingContainer(SystemLoggingContainer logger)357     public static void setLoggingContainer(SystemLoggingContainer logger) {
358         systemLogger = logger;
359     }
360 
361     // Overridden in LogTest to skip query to ContentProvider
362     public interface ISessionCleanupTimeoutMs {
get()363         long get();
364     }
365     @VisibleForTesting
366     public static ISessionCleanupTimeoutMs sSessionCleanupTimeoutMs =
367             new ISessionCleanupTimeoutMs() {
368                 @Override
369                 public long get() {
370                     // mContext will be null if Log is called from another process
371                     // (UserCallActivity, for example). For these cases, use the default value.
372                     if(mContext == null) {
373                         return DEFAULT_SESSION_TIMEOUT_MS;
374                     }
375                     return Timeouts.getStaleSessionCleanupTimeoutMillis(
376                             mContext.getContentResolver());
377                 }
378             };
379 
getSessionCleanupTimeoutMs()380     private static long getSessionCleanupTimeoutMs() {
381         return sSessionCleanupTimeoutMs.get();
382     }
383 
resetStaleSessionTimer()384     private static synchronized void resetStaleSessionTimer() {
385         sSessionCleanupHandler.removeCallbacksAndMessages(null);
386         // Will be null in Log Testing
387         if (sCleanStaleSessions != null) {
388             sSessionCleanupHandler.postDelayed(sCleanStaleSessions, getSessionCleanupTimeoutMs());
389         }
390     }
391 
392     /**
393      * Call at an entry point to the Telecom code to track the session. This code must be
394      * accompanied by a Log.endSession().
395      */
startSession(String shortMethodName)396     public static synchronized void startSession(String shortMethodName) {
397         startSession(shortMethodName, null);
398     }
startSession(String shortMethodName, String callerIdentification)399     public static synchronized void startSession(String shortMethodName,
400             String callerIdentification) {
401         resetStaleSessionTimer();
402         int threadId = getCallingThreadId();
403         Session activeSession = sSessionMapper.get(threadId);
404         // We have called startSession within an active session that has not ended... Register this
405         // session as a subsession.
406         if (activeSession != null) {
407             Session childSession = createSubsession(true);
408             continueSession(childSession, shortMethodName);
409             return;
410         }
411         Session newSession = new Session(getNextSessionID(), shortMethodName,
412                 System.currentTimeMillis(), threadId, false, callerIdentification);
413         sSessionMapper.put(threadId, newSession);
414 
415         Log.v(LOGGING_TAG, Session.START_SESSION);
416     }
417 
418 
419     /**
420      * Notifies the logging system that a subsession will be run at a later point and
421      * allocates the resources. Returns a session object that must be used in
422      * Log.continueSession(...) to start the subsession.
423      */
createSubsession()424     public static Session createSubsession() {
425         return createSubsession(false);
426     }
427 
createSubsession(boolean isStartedFromActiveSession)428     private static synchronized Session createSubsession(boolean isStartedFromActiveSession) {
429         int threadId = getCallingThreadId();
430         Session threadSession = sSessionMapper.get(threadId);
431         if (threadSession == null) {
432             Log.d(LOGGING_TAG, "Log.createSubsession was called with no session active.");
433             return null;
434         }
435         // Start execution time of the session will be overwritten in continueSession(...).
436         Session newSubsession = new Session(threadSession.getNextChildId(),
437                 threadSession.getShortMethodName(), System.currentTimeMillis(), threadId,
438                 isStartedFromActiveSession, null);
439         threadSession.addChild(newSubsession);
440         newSubsession.setParentSession(threadSession);
441 
442         if(!isStartedFromActiveSession) {
443             Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + newSubsession.toString());
444         } else {
445             Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)");
446         }
447         return newSubsession;
448     }
449 
450     /**
451      * Cancels a subsession that had Log.createSubsession() called on it, but will never have
452      * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
453      * gracefully instead of being removed by the sSessionCleanupHandler forcefully later.
454      */
cancelSubsession(Session subsession)455     public static synchronized void cancelSubsession(Session subsession) {
456         if (subsession == null) {
457             return;
458         }
459 
460         subsession.markSessionCompleted(0);
461         endParentSessions(subsession);
462     }
463 
464     /**
465      * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method
466      * must be called at the end of this method. The full session will complete when all
467      * subsessions are completed.
468      */
continueSession(Session subsession, String shortMethodName)469     public static synchronized void continueSession(Session subsession, String shortMethodName) {
470         if (subsession == null) {
471             return;
472         }
473         resetStaleSessionTimer();
474         String callingMethodName = subsession.getShortMethodName();
475         subsession.setShortMethodName(callingMethodName + "->" + shortMethodName);
476         subsession.setExecutionStartTimeMs(System.currentTimeMillis());
477         Session parentSession = subsession.getParentSession();
478         if (parentSession == null) {
479             Log.d(LOGGING_TAG, "Log.continueSession was called with no session active for " +
480                     "method %s.", shortMethodName);
481             return;
482         }
483 
484         sSessionMapper.put(getCallingThreadId(), subsession);
485         if(!subsession.isStartedFromActiveSession()) {
486             Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION);
487         } else {
488             Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION + " (Invisible Subsession) with " +
489                     "Method " + shortMethodName);
490         }
491     }
492 
checkIsThreadLogged()493     public static void checkIsThreadLogged() {
494         int threadId = getCallingThreadId();
495         Session threadSession = sSessionMapper.get(threadId);
496         if (threadSession == null) {
497             android.util.Log.e(LOGGING_TAG, "Logging Thread Check Failed!", new Exception());
498         }
499     }
500 
501     /**
502      * Ends the current session/subsession. Must be called after a Log.startSession(...) and
503      * Log.continueSession(...) call.
504      */
endSession()505     public static synchronized void endSession() {
506         int threadId = getCallingThreadId();
507         Session completedSession = sSessionMapper.get(threadId);
508         if (completedSession == null) {
509             Log.w(LOGGING_TAG, "Log.endSession was called with no session active.");
510             return;
511         }
512 
513         completedSession.markSessionCompleted(System.currentTimeMillis());
514         if(!completedSession.isStartedFromActiveSession()) {
515             Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " +
516                     completedSession.getLocalExecutionTime() + " mS)");
517         } else {
518             Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (Invisible Subsession) (dur: " +
519                     completedSession.getLocalExecutionTime() + " mS)");
520         }
521         // Remove after completed so that reference still exists for logging the end events
522         Session parentSession = completedSession.getParentSession();
523         sSessionMapper.remove(threadId);
524         endParentSessions(completedSession);
525         // If this subsession was started from a parent session using Log.startSession, return the
526         // ThreadID back to the parent after completion.
527         if (parentSession != null && !parentSession.isSessionCompleted() &&
528                 completedSession.isStartedFromActiveSession()) {
529             sSessionMapper.put(threadId, parentSession);
530         }
531     }
532 
533     // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
endParentSessions(Session subsession)534     private static void endParentSessions(Session subsession) {
535         // Session is not completed or not currently a leaf, so we can not remove because a child is
536         // still running
537         if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
538             return;
539         }
540 
541         Session parentSession = subsession.getParentSession();
542         if (parentSession != null) {
543             subsession.setParentSession(null);
544             parentSession.removeChild(subsession);
545             endParentSessions(parentSession);
546         } else {
547             // All of the subsessions have been completed and it is time to report on the full
548             // running time of the session.
549             long fullSessionTimeMs =
550                     System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
551             Log.v(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + " ms): " +
552                     subsession.toString());
553         }
554     }
555 
getNextSessionID()556     private synchronized static String getNextSessionID() {
557         Integer nextId = sCodeEntryCounter++;
558         if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) {
559             restartSessionCounter();
560             nextId = sCodeEntryCounter++;
561         }
562         return getBase64Encoding(nextId);
563     }
564 
565     @VisibleForTesting
restartSessionCounter()566     public synchronized static void restartSessionCounter() {
567         sCodeEntryCounter = 0;
568     }
569 
570     @VisibleForTesting
getBase64Encoding(int number)571     public static String getBase64Encoding(int number) {
572         byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array();
573         idByteArray = Arrays.copyOfRange(idByteArray, 2, 4);
574         return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING);
575     }
576 
getCallingThreadId()577     public static int getCallingThreadId() {
578         return android.os.Process.myTid();
579     }
580 
event(Call call, String event)581     public static void event(Call call, String event) {
582         event(call, event, null);
583     }
584 
event(Call call, String event, Object data)585     public static void event(Call call, String event, Object data) {
586         Session currentSession = sSessionMapper.get(getCallingThreadId());
587         String currentSessionID = currentSession != null ? currentSession.toString() : "";
588 
589         if (call == null) {
590             Log.i(TAG, "Non-call EVENT: %s, %s", event, data);
591             return;
592         }
593         synchronized (mCallEventRecords) {
594             if (!mCallEventRecordMap.containsKey(call)) {
595                 CallEventRecord newRecord = new CallEventRecord(call);
596                 addCallEventRecord(newRecord);
597             }
598 
599             CallEventRecord record = mCallEventRecordMap.get(call);
600             record.addEvent(event, currentSessionID, data);
601         }
602     }
603 
604     @VisibleForTesting
cleanupStaleSessions(long timeoutMs)605     public static synchronized void cleanupStaleSessions(long timeoutMs) {
606         String logMessage = "Stale Sessions Cleaned:\n";
607         boolean isSessionsStale = false;
608         long currentTimeMs = System.currentTimeMillis();
609         // Remove references that are in the Session Mapper (causing GC to occur) on
610         // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
611         // If this occurs, then there is most likely a Session active that never had
612         // Log.endSession called on it.
613         for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
614              sSessionMapper.entrySet().iterator(); it.hasNext(); ) {
615             ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
616             Session session = entry.getValue();
617             if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
618                 it.remove();
619                 logMessage += session.printFullSessionTree() + "\n";
620                 isSessionsStale = true;
621             }
622         }
623         if (isSessionsStale) {
624             Log.w(LOGGING_TAG, logMessage);
625         } else {
626             Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
627         }
628     }
629 
addCallEventRecord(CallEventRecord newRecord)630     private static void addCallEventRecord(CallEventRecord newRecord) {
631         Call call = newRecord.getCall();
632 
633         // First remove the oldest entry if no new ones exist.
634         if (mCallEventRecords.remainingCapacity() == 0) {
635             CallEventRecord record = mCallEventRecords.poll();
636             if (record != null) {
637                 mCallEventRecordMap.remove(record.getCall());
638             }
639         }
640 
641         // Now add a new entry
642         mCallEventRecords.add(newRecord);
643         mCallEventRecordMap.put(call, newRecord);
644     }
645 
646     /**
647      * If user enabled extended logging is enabled and the time limit has passed, disables the
648      * extended logging.
649      */
maybeDisableLogging()650     private static void maybeDisableLogging() {
651         if (!mIsUserExtendedLoggingEnabled) {
652             return;
653         }
654 
655         if (mUserExtendedLoggingStopTime < System.currentTimeMillis()) {
656             mUserExtendedLoggingStopTime = 0;
657             mIsUserExtendedLoggingEnabled = false;
658         }
659     }
660 
isLoggable(int level)661     public static boolean isLoggable(int level) {
662         return FORCE_LOGGING || android.util.Log.isLoggable(TAG, level);
663     }
664 
d(String prefix, String format, Object... args)665     public static void d(String prefix, String format, Object... args) {
666         if (mIsUserExtendedLoggingEnabled) {
667             maybeDisableLogging();
668             systemLogger.i(TAG, buildMessage(prefix, format, args));
669         } else if (DEBUG) {
670             systemLogger.d(TAG, buildMessage(prefix, format, args));
671         }
672     }
673 
d(Object objectPrefix, String format, Object... args)674     public static void d(Object objectPrefix, String format, Object... args) {
675         if (mIsUserExtendedLoggingEnabled) {
676             maybeDisableLogging();
677             systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
678         } else if (DEBUG) {
679             systemLogger.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
680         }
681     }
682 
i(String prefix, String format, Object... args)683     public static void i(String prefix, String format, Object... args) {
684         if (INFO) {
685             systemLogger.i(TAG, buildMessage(prefix, format, args));
686         }
687     }
688 
i(Object objectPrefix, String format, Object... args)689     public static void i(Object objectPrefix, String format, Object... args) {
690         if (INFO) {
691             systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
692         }
693     }
694 
v(String prefix, String format, Object... args)695     public static void v(String prefix, String format, Object... args) {
696         if (mIsUserExtendedLoggingEnabled) {
697             maybeDisableLogging();
698             systemLogger.i(TAG, buildMessage(prefix, format, args));
699         } else if (VERBOSE) {
700             systemLogger.v(TAG, buildMessage(prefix, format, args));
701         }
702     }
703 
v(Object objectPrefix, String format, Object... args)704     public static void v(Object objectPrefix, String format, Object... args) {
705         if (mIsUserExtendedLoggingEnabled) {
706             maybeDisableLogging();
707             systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
708         } else if (VERBOSE) {
709             systemLogger.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
710         }
711     }
712 
w(String prefix, String format, Object... args)713     public static void w(String prefix, String format, Object... args) {
714         if (WARN) {
715             systemLogger.w(TAG, buildMessage(prefix, format, args));
716         }
717     }
718 
w(Object objectPrefix, String format, Object... args)719     public static void w(Object objectPrefix, String format, Object... args) {
720         if (WARN) {
721             systemLogger.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
722         }
723     }
724 
e(String prefix, Throwable tr, String format, Object... args)725     public static void e(String prefix, Throwable tr, String format, Object... args) {
726         if (ERROR) {
727             systemLogger.e(TAG, buildMessage(prefix, format, args), tr);
728         }
729     }
730 
e(Object objectPrefix, Throwable tr, String format, Object... args)731     public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
732         if (ERROR) {
733             systemLogger.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
734                     tr);
735         }
736     }
737 
wtf(String prefix, Throwable tr, String format, Object... args)738     public static void wtf(String prefix, Throwable tr, String format, Object... args) {
739         systemLogger.wtf(TAG, buildMessage(prefix, format, args), tr);
740     }
741 
wtf(Object objectPrefix, Throwable tr, String format, Object... args)742     public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
743         systemLogger.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
744                 tr);
745     }
746 
wtf(String prefix, String format, Object... args)747     public static void wtf(String prefix, String format, Object... args) {
748         String msg = buildMessage(prefix, format, args);
749         systemLogger.wtf(TAG, msg, new IllegalStateException(msg));
750     }
751 
wtf(Object objectPrefix, String format, Object... args)752     public static void wtf(Object objectPrefix, String format, Object... args) {
753         String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
754         systemLogger.wtf(TAG, msg, new IllegalStateException(msg));
755     }
756 
piiHandle(Object pii)757     public static String piiHandle(Object pii) {
758         if (pii == null || VERBOSE) {
759             return String.valueOf(pii);
760         }
761 
762         StringBuilder sb = new StringBuilder();
763         if (pii instanceof Uri) {
764             Uri uri = (Uri) pii;
765             String scheme = uri.getScheme();
766 
767             if (!TextUtils.isEmpty(scheme)) {
768                 sb.append(scheme).append(":");
769             }
770 
771             String textToObfuscate = uri.getSchemeSpecificPart();
772             if (PhoneAccount.SCHEME_TEL.equals(scheme)) {
773                 for (int i = 0; i < textToObfuscate.length(); i++) {
774                     char c = textToObfuscate.charAt(i);
775                     sb.append(PhoneNumberUtils.isDialable(c) ? "*" : c);
776                 }
777             } else if (PhoneAccount.SCHEME_SIP.equals(scheme)) {
778                 for (int i = 0; i < textToObfuscate.length(); i++) {
779                     char c = textToObfuscate.charAt(i);
780                     if (c != '@' && c != '.') {
781                         c = '*';
782                     }
783                     sb.append(c);
784                 }
785             } else {
786                 sb.append(pii(pii));
787             }
788         }
789 
790         return sb.toString();
791     }
792 
793     /**
794      * Redact personally identifiable information for production users.
795      * If we are running in verbose mode, return the original string, otherwise
796      * return a SHA-1 hash of the input string.
797      */
pii(Object pii)798     public static String pii(Object pii) {
799         if (pii == null || VERBOSE) {
800             return String.valueOf(pii);
801         }
802         return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
803     }
804 
dumpCallEvents(IndentingPrintWriter pw)805     public static void dumpCallEvents(IndentingPrintWriter pw) {
806         pw.println("Historical Calls:");
807         pw.increaseIndent();
808         for (CallEventRecord callEventRecord : mCallEventRecords) {
809             callEventRecord.dump(pw);
810         }
811         pw.decreaseIndent();
812     }
813 
secureHash(byte[] input)814     private static String secureHash(byte[] input) {
815         if (sMessageDigest != null) {
816             sMessageDigest.reset();
817             sMessageDigest.update(input);
818             byte[] result = sMessageDigest.digest();
819             return encodeHex(result);
820         } else {
821             return "Uninitialized SHA1";
822         }
823     }
824 
encodeHex(byte[] bytes)825     private static String encodeHex(byte[] bytes) {
826         StringBuffer hex = new StringBuffer(bytes.length * 2);
827 
828         for (int i = 0; i < bytes.length; i++) {
829             int byteIntValue = bytes[i] & 0xff;
830             if (byteIntValue < 0x10) {
831                 hex.append("0");
832             }
833             hex.append(Integer.toString(byteIntValue, 16));
834         }
835 
836         return hex.toString();
837     }
838 
getPrefixFromObject(Object obj)839     private static String getPrefixFromObject(Object obj) {
840         return obj == null ? "<null>" : obj.getClass().getSimpleName();
841     }
842 
buildMessage(String prefix, String format, Object... args)843     private static String buildMessage(String prefix, String format, Object... args) {
844         if (LOG_DBG) {
845             checkIsThreadLogged();
846         }
847         // Incorporate thread ID and calling method into prefix
848         String sessionPostfix = "";
849         Session currentSession = sSessionMapper.get(getCallingThreadId());
850         if (currentSession != null) {
851             sessionPostfix = ": " + currentSession.toString();
852         }
853 
854         String msg;
855         try {
856             msg = (args == null || args.length == 0) ? format
857                     : String.format(Locale.US, format, args);
858         } catch (IllegalFormatException ife) {
859             e("Log", ife, "IllegalFormatException: formatString='%s' numArgs=%d", format,
860                     args.length);
861             msg = format + " (An error occurred while formatting the message.)";
862         }
863         return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
864     }
865 }
866