151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger/*
251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * Copyright (C) 2016 The Android Open Source Project
351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger *
451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * Licensed under the Apache License, Version 2.0 (the "License");
551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * you may not use this file except in compliance with the License.
651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * You may obtain a copy of the License at
751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger *
851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger *      http://www.apache.org/licenses/LICENSE-2.0
951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger *
1051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * Unless required by applicable law or agreed to in writing, software
1151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * distributed under the License is distributed on an "AS IS" BASIS,
1251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * See the License for the specific language governing permissions and
1451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * limitations under the License
1551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger */
1651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
1751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerpackage android.telecom.Logging;
1851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
1951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport android.content.Context;
2051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport android.os.Handler;
2151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport android.os.Looper;
22096d2829edd2cda66a004ea7216975730981814eBrad Ebingerimport android.os.Process;
2351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport android.provider.Settings;
24a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebingerimport android.telecom.Log;
2551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport android.util.Base64;
2651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
2751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport com.android.internal.annotations.VisibleForTesting;
2851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
2951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.nio.ByteBuffer;
3051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.util.ArrayList;
3151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.util.Arrays;
3251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.util.Iterator;
3351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.util.List;
3451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerimport java.util.concurrent.ConcurrentHashMap;
3551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
3651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger/**
3751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * TODO: Create better Sessions Documentation
3851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger * @hide
3951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger */
4051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
4151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebingerpublic class SessionManager {
4251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
4351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // Currently using 3 letters, So don't exceed 64^3
4451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144;
4551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // This parameter can be overridden in Telecom's Timeouts class.
46a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
47a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private static final String LOGGING_TAG = "Logging";
4851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private static final String TIMEOUTS_PREFIX = "telecom.";
4951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
5051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // Synchronized in all method calls
5151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private int sCodeEntryCounter = 0;
5251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private Context mContext;
5351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
5451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    @VisibleForTesting
55096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    public ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(100);
5651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    @VisibleForTesting
57096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    public java.lang.Runnable mCleanStaleSessions = () ->
5851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            cleanupStaleSessions(getSessionCleanupTimeoutMs());
59a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
6051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
6151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // Overridden in LogTest to skip query to ContentProvider
6251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private interface ISessionCleanupTimeoutMs {
6351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        long get();
6451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
6551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
66096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    // Overridden in tests to provide test Thread IDs
67096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    public interface ICurrentThreadId {
68096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        int get();
69096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    }
70096d2829edd2cda66a004ea7216975730981814eBrad Ebinger
71096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    @VisibleForTesting
72096d2829edd2cda66a004ea7216975730981814eBrad Ebinger    public ICurrentThreadId mCurrentThreadId = Process::myTid;
73096d2829edd2cda66a004ea7216975730981814eBrad Ebinger
74a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private ISessionCleanupTimeoutMs mSessionCleanupTimeoutMs = () -> {
7551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // mContext may be null in some cases, such as testing. For these cases, use the
7651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // default value.
7751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (mContext == null) {
7851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return DEFAULT_SESSION_TIMEOUT_MS;
7951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
8051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return getCleanupTimeout(mContext);
8151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    };
8251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
8351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // Usage is synchronized on this class.
8451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private List<ISessionListener> mSessionListeners = new ArrayList<>();
8551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
8651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public interface ISessionListener {
8751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        /**
8851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger         * This method is run when a full Session has completed.
8951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger         * @param sessionName The name of the Session that has completed.
9051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger         * @param timeMs The time it took to complete in ms.
9151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger         */
9251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        void sessionComplete(String sessionName, long timeMs);
9351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
9451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
9551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public interface ISessionIdQueryHandler {
9651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        String getSessionId();
9751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
9851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
9951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public void setContext(Context context) {
10051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        mContext = context;
10151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
10251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
10351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public SessionManager() {
10451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
10551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
10651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private long getSessionCleanupTimeoutMs() {
107096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        return mSessionCleanupTimeoutMs.get();
10851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
10951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
11051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private synchronized void resetStaleSessionTimer() {
111096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        mSessionCleanupHandler.removeCallbacksAndMessages(null);
11251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Will be null in Log Testing
113096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        if (mCleanStaleSessions != null) {
114096d2829edd2cda66a004ea7216975730981814eBrad Ebinger            mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
11551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
11651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
11751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
11851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
119a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * Determines whether or not to start a new session or continue an existing session based on
120a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * the {@link Session.Info} info passed into startSession. If info is null, a new Session is
121a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * created. This code must be accompanied by endSession() at the end of the Session.
122a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     */
123a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    public synchronized void startSession(Session.Info info, String shortMethodName,
124a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            String callerIdentification) {
125a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // Start a new session normally if the
126a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        if(info == null) {
127a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            startSession(shortMethodName, callerIdentification);
128a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        } else {
129a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            startExternalSession(info, shortMethodName);
130a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        }
131a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    }
132a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger
133a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    /**
13451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Call at an entry point to the Telecom code to track the session. This code must be
13551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * accompanied by a Log.endSession().
13651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
13751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public synchronized void startSession(String shortMethodName,
13851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            String callerIdentification) {
13951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        resetStaleSessionTimer();
14051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        int threadId = getCallingThreadId();
141096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        Session activeSession = mSessionMapper.get(threadId);
14251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // We have called startSession within an active session that has not ended... Register this
14351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // session as a subsession.
14451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (activeSession != null) {
14551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            Session childSession = createSubsession(true);
14651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            continueSession(childSession, shortMethodName);
14751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
148a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        } else {
149a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            // Only Log that we are starting the parent session.
150a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.d(LOGGING_TAG, Session.START_SESSION);
15151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
15251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Session newSession = new Session(getNextSessionID(), shortMethodName,
153a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                System.currentTimeMillis(), false, callerIdentification);
154096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        mSessionMapper.put(threadId, newSession);
15551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
15651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
157a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    /**
158a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * Registers an external Session with the Manager using that external Session's sessionInfo.
159a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * Log.endSession will still need to be called at the end of the session.
160a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * @param sessionInfo Describes the external Session's information.
161a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     * @param shortMethodName The method name of the new session that is being started.
162a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger     */
163a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    public synchronized void startExternalSession(Session.Info sessionInfo,
164a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            String shortMethodName) {
165a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        if(sessionInfo == null) {
166a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            return;
167a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        }
168a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger
169a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        int threadId = getCallingThreadId();
170a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        Session threadSession = mSessionMapper.get(threadId);
171a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        if (threadSession != null) {
172a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            // We should never get into a situation where there is already an active session AND
173a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            // an external session is added. We are just using that active session.
174a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.w(LOGGING_TAG, "trying to start an external session with a session " +
175a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                    "already active.");
176a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            return;
177a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        }
178a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger
179a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // Create Session from Info and add to the sessionMapper under this ID.
1800c3541be65fa87519a879c053a7cf4b4526be5dbBrad Ebinger        Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION);
181a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId,
1820c3541be65fa87519a879c053a7cf4b4526be5dbBrad Ebinger                sessionInfo.methodPath, System.currentTimeMillis(),
183a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                false /*isStartedFromActiveSession*/, null);
184a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        externalSession.setIsExternal(true);
185a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // Mark the external session as already completed, since we have no way of knowing when
186a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // the external session actually has completed.
187a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        externalSession.markSessionCompleted(Session.UNDEFINED);
188a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // Track the external session with the SessionMapper so that we can create and continue
189a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // an active subsession based on it.
190a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        mSessionMapper.put(threadId, externalSession);
191a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        // Create a subsession from this external Session parent node
192a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        Session childSession = createSubsession();
193a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        continueSession(childSession, shortMethodName);
194a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    }
19551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
19651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
19751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Notifies the logging system that a subsession will be run at a later point and
19851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * allocates the resources. Returns a session object that must be used in
19951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Log.continueSession(...) to start the subsession.
20051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
20151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public Session createSubsession() {
20251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return createSubsession(false);
20351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
20451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
20551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private synchronized Session createSubsession(boolean isStartedFromActiveSession) {
20651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        int threadId = getCallingThreadId();
207096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        Session threadSession = mSessionMapper.get(threadId);
20851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (threadSession == null) {
209a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.d(LOGGING_TAG, "Log.createSubsession was called with no session " +
21051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    "active.");
21151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return null;
21251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
21351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Start execution time of the session will be overwritten in continueSession(...).
21451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Session newSubsession = new Session(threadSession.getNextChildId(),
215a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                threadSession.getShortMethodName(), System.currentTimeMillis(),
21651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                isStartedFromActiveSession, null);
21751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        threadSession.addChild(newSubsession);
21851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        newSubsession.setParentSession(threadSession);
21951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
22051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (!isStartedFromActiveSession) {
221a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " +
22251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    newSubsession.toString());
22351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        } else {
224a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION +
22551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    " (Invisible subsession)");
22651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
22751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return newSubsession;
22851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
22951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
23051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
2313445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger     * Retrieve the information of the currently active Session. This information is parcelable and
2323445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger     * is used to create an external Session ({@link #startExternalSession(Session.Info, String)}).
2333445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger     * If there is no Session active, this method will return null.
2343445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger     */
2353445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger    public synchronized Session.Info getExternalSession() {
2363445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger        int threadId = getCallingThreadId();
2373445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger        Session threadSession = mSessionMapper.get(threadId);
2383445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger        if (threadSession == null) {
2393445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger            Log.d(LOGGING_TAG, "Log.getExternalSession was called with no session " +
2403445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger                    "active.");
2413445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger            return null;
2423445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger        }
2433445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger
2443445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger        return threadSession.getInfo();
2453445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger    }
2463445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger
2473445f829077cea72da77e31f0a2f6ccce3af295bBrad Ebinger    /**
24851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Cancels a subsession that had Log.createSubsession() called on it, but will never have
24951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
250096d2829edd2cda66a004ea7216975730981814eBrad Ebinger     * gracefully instead of being removed by the mSessionCleanupHandler forcefully later.
25151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
25251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public synchronized void cancelSubsession(Session subsession) {
25351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (subsession == null) {
25451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
25551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
25651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
257096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        subsession.markSessionCompleted(Session.UNDEFINED);
25851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        endParentSessions(subsession);
25951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
26051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
26151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
26251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method
26351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * must be called at the end of this method. The full session will complete when all
26451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * subsessions are completed.
26551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
26651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public synchronized void continueSession(Session subsession, String shortMethodName) {
26751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (subsession == null) {
26851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
26951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
27051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        resetStaleSessionTimer();
271a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        subsession.setShortMethodName(shortMethodName);
27251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        subsession.setExecutionStartTimeMs(System.currentTimeMillis());
27351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Session parentSession = subsession.getParentSession();
27451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (parentSession == null) {
275a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.i(LOGGING_TAG, "Log.continueSession was called with no session " +
276096d2829edd2cda66a004ea7216975730981814eBrad Ebinger                    "active for method " + shortMethodName);
27751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
27851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
27951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
280096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        mSessionMapper.put(getCallingThreadId(), subsession);
28151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (!subsession.isStartedFromActiveSession()) {
282a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION);
28351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        } else {
284a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION +
28551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    " (Invisible Subsession) with Method " + shortMethodName);
28651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
28751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
28851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
28951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
29051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Ends the current session/subsession. Must be called after a Log.startSession(...) and
29151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Log.continueSession(...) call.
29251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
29351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public synchronized void endSession() {
29451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        int threadId = getCallingThreadId();
295096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        Session completedSession = mSessionMapper.get(threadId);
29651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (completedSession == null) {
297a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.w(LOGGING_TAG, "Log.endSession was called with no session active.");
29851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
29951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
30051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
30151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        completedSession.markSessionCompleted(System.currentTimeMillis());
30251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (!completedSession.isStartedFromActiveSession()) {
303a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " +
30451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    completedSession.getLocalExecutionTime() + " mS)");
30551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        } else {
306a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, Session.END_SUBSESSION +
30751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    " (Invisible Subsession) (dur: " + completedSession.getLocalExecutionTime() +
30851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    " ms)");
30951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
31051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Remove after completed so that reference still exists for logging the end events
31151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Session parentSession = completedSession.getParentSession();
312096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        mSessionMapper.remove(threadId);
31351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        endParentSessions(completedSession);
31451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // If this subsession was started from a parent session using Log.startSession, return the
31551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // ThreadID back to the parent after completion.
31651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (parentSession != null && !parentSession.isSessionCompleted() &&
31751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                completedSession.isStartedFromActiveSession()) {
318096d2829edd2cda66a004ea7216975730981814eBrad Ebinger            mSessionMapper.put(threadId, parentSession);
31951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
32051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
32151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
32251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
32351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private void endParentSessions(Session subsession) {
32451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Session is not completed or not currently a leaf, so we can not remove because a child is
32551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // still running
32651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
32751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            return;
32851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
32951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Session parentSession = subsession.getParentSession();
33051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (parentSession != null) {
33151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            subsession.setParentSession(null);
33251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            parentSession.removeChild(subsession);
333a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            // Report the child session of the external session as being complete to the listeners,
334a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            // not the external session itself.
335a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            if (parentSession.isExternal()) {
336a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                long fullSessionTimeMs =
337a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                        System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
338a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs);
339a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            }
34051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            endParentSessions(parentSession);
34151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        } else {
34251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            // All of the subsessions have been completed and it is time to report on the full
34351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            // running time of the session.
34451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            long fullSessionTimeMs =
34551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
346a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs
34751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                    + " ms): " + subsession.toString());
348a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            if (!subsession.isExternal()) {
349a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger                notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs);
35051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            }
35151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
35251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
35351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
354a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) {
355a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        for (ISessionListener l : mSessionListeners) {
356a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            l.sessionComplete(methodName, sessionTimeMs);
357a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger        }
358a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    }
359a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger
36051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public String getSessionId() {
361096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        Session currentSession = mSessionMapper.get(getCallingThreadId());
36251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return currentSession != null ? currentSession.toString() : "";
36351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
36451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
36551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    public synchronized void registerSessionListener(ISessionListener l) {
36651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (l != null) {
36751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            mSessionListeners.add(l);
36851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
36951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
37051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
37151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private synchronized String getNextSessionID() {
37251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        Integer nextId = sCodeEntryCounter++;
37351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) {
37451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            restartSessionCounter();
37551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            nextId = sCodeEntryCounter++;
37651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
37751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return getBase64Encoding(nextId);
37851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
37951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
380a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private synchronized void restartSessionCounter() {
38151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        sCodeEntryCounter = 0;
38251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
38351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
384a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private String getBase64Encoding(int number) {
38551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array();
38651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        idByteArray = Arrays.copyOfRange(idByteArray, 2, 4);
38751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING);
38851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
38951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
390a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    private int getCallingThreadId() {
391096d2829edd2cda66a004ea7216975730981814eBrad Ebinger        return mCurrentThreadId.get();
39251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
39351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
39451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    @VisibleForTesting
395a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger    public synchronized void cleanupStaleSessions(long timeoutMs) {
39651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        String logMessage = "Stale Sessions Cleaned:\n";
39751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        boolean isSessionsStale = false;
39851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        long currentTimeMs = System.currentTimeMillis();
39951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Remove references that are in the Session Mapper (causing GC to occur) on
40051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
40151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // If this occurs, then there is most likely a Session active that never had
40251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        // Log.endSession called on it.
40351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
404096d2829edd2cda66a004ea7216975730981814eBrad Ebinger             mSessionMapper.entrySet().iterator(); it.hasNext(); ) {
40551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
40651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            Session session = entry.getValue();
40751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
40851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                it.remove();
40951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                logMessage += session.printFullSessionTree() + "\n";
41051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                isSessionsStale = true;
41151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger            }
41251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
41351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        if (isSessionsStale) {
414a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.w(LOGGING_TAG, logMessage);
41551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        } else {
416a0dc9765d339ee69da4a1adc3bd6863126267b08Brad Ebinger            Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
41751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        }
41851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
41951b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger
42051b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    /**
42151b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * Returns the amount of time after a Logging session has been started that Telecom is set to
42251b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     * perform a sweep to check and make sure that the session is still not incomplete (stale).
42351b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger     */
42451b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    private long getCleanupTimeout(Context context) {
42551b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger        return Settings.Secure.getLong(context.getContentResolver(), TIMEOUTS_PREFIX +
42651b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger                "stale_session_cleanup_timeout_millis", DEFAULT_SESSION_TIMEOUT_MS);
42751b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger    }
42851b9834180db6ecaf4edaf38fb12d5d408f2c1ceBrad Ebinger}
429