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