Session.java revision 096d2829edd2cda66a004ea7216975730981814e
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.telecom.Logging;
18
19import android.annotation.NonNull;
20
21import java.util.ArrayList;
22
23/**
24 * The session that stores information about a thread's point of entry into the Telecom code that
25 * persists until the thread exits Telecom.
26 * @hide
27 */
28public class Session {
29
30    public static final String START_SESSION = "START_SESSION";
31    public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
32    public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
33    public static final String END_SUBSESSION = "END_SUBSESSION";
34    public static final String END_SESSION = "END_SESSION";
35
36    /**
37     * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
38     * if the Session is canceled.
39     */
40    public static final int UNDEFINED = -1;
41
42    private String mSessionId;
43    private String mShortMethodName;
44    private long mExecutionStartTimeMs;
45    private long mExecutionEndTimeMs = UNDEFINED;
46    private Session mParentSession;
47    private ArrayList<Session> mChildSessions;
48    private boolean mIsCompleted = false;
49    private int mChildCounter = 0;
50    // True if this is a subsession that has been started from the same thread as the parent
51    // session. This can happen if Log.startSession(...) is called multiple times on the same
52    // thread in the case of one Telecom entry point method calling another entry point method.
53    // In this case, we can just make this subsession "invisible," but still keep track of it so
54    // that the Log.endSession() calls match up.
55    private boolean mIsStartedFromActiveSession = false;
56    // Optionally provided info about the method/class/component that started the session in order
57    // to make Logging easier. This info will be provided in parentheses along with the session.
58    private String mOwnerInfo;
59
60    public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID,
61            boolean isStartedFromActiveSession, String ownerInfo) {
62        setSessionId(sessionId);
63        setShortMethodName(shortMethodName);
64        mExecutionStartTimeMs = startTimeMs;
65        mParentSession = null;
66        mChildSessions = new ArrayList<>(5);
67        mIsStartedFromActiveSession = isStartedFromActiveSession;
68        mOwnerInfo = ownerInfo;
69    }
70
71    public void setSessionId(@NonNull String sessionId) {
72        if (sessionId == null) {
73            mSessionId = "?";
74        }
75        mSessionId = sessionId;
76    }
77
78    public String getShortMethodName() {
79        return mShortMethodName;
80    }
81
82    public void setShortMethodName(String shortMethodName) {
83        if (shortMethodName == null) {
84            shortMethodName = "";
85        }
86        mShortMethodName = shortMethodName;
87    }
88
89    public void setParentSession(Session parentSession) {
90        mParentSession = parentSession;
91    }
92
93    public void addChild(Session childSession) {
94        if (childSession != null) {
95            mChildSessions.add(childSession);
96        }
97    }
98
99    public void removeChild(Session child) {
100        if (child != null) {
101            mChildSessions.remove(child);
102        }
103    }
104
105    public long getExecutionStartTimeMilliseconds() {
106        return mExecutionStartTimeMs;
107    }
108
109    public void setExecutionStartTimeMs(long startTimeMs) {
110        mExecutionStartTimeMs = startTimeMs;
111    }
112
113    public Session getParentSession() {
114        return mParentSession;
115    }
116
117    public ArrayList<Session> getChildSessions() {
118        return mChildSessions;
119    }
120
121    public boolean isSessionCompleted() {
122        return mIsCompleted;
123    }
124
125    public boolean isStartedFromActiveSession() {
126        return mIsStartedFromActiveSession;
127    }
128
129    // Mark this session complete. This will be deleted by Log when all subsessions are complete
130    // as well.
131    public void markSessionCompleted(long executionEndTimeMs) {
132        mExecutionEndTimeMs = executionEndTimeMs;
133        mIsCompleted = true;
134    }
135
136    public long getLocalExecutionTime() {
137        if (mExecutionEndTimeMs == UNDEFINED) {
138            return UNDEFINED;
139        }
140        return mExecutionEndTimeMs - mExecutionStartTimeMs;
141    }
142
143    public synchronized String getNextChildId() {
144        return String.valueOf(mChildCounter++);
145    }
146
147    // Builds full session id recursively
148    private String getFullSessionId() {
149        // Cache mParentSession locally to prevent a concurrency problem where
150        // Log.endParentSessions() is called while a logging statement is running (Log.i, for
151        // example) and setting mParentSession to null in a different thread after the null check
152        // occurred.
153        Session parentSession = mParentSession;
154        if (parentSession == null) {
155            return mSessionId;
156        } else {
157            return parentSession.getFullSessionId() + "_" + mSessionId;
158        }
159    }
160
161    // Print out the full Session tree from any subsession node
162    public String printFullSessionTree() {
163        // Get to the top of the tree
164        Session topNode = this;
165        while (topNode.getParentSession() != null) {
166            topNode = topNode.getParentSession();
167        }
168        return topNode.printSessionTree();
169    }
170
171    // Recursively move down session tree using DFS, but print out each node when it is reached.
172    public String printSessionTree() {
173        StringBuilder sb = new StringBuilder();
174        printSessionTree(0, sb);
175        return sb.toString();
176    }
177
178    private void printSessionTree(int tabI, StringBuilder sb) {
179        sb.append(toString());
180        for (Session child : mChildSessions) {
181            sb.append("\n");
182            for (int i = 0; i <= tabI; i++) {
183                sb.append("\t");
184            }
185            child.printSessionTree(tabI + 1, sb);
186        }
187    }
188
189    @Override
190    public int hashCode() {
191        int result = mSessionId != null ? mSessionId.hashCode() : 0;
192        result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0);
193        result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32));
194        result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32));
195        result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0);
196        result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0);
197        result = 31 * result + (mIsCompleted ? 1 : 0);
198        result = 31 * result + mChildCounter;
199        result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0);
200        result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0);
201        return result;
202    }
203
204    @Override
205    public boolean equals(Object o) {
206        if (this == o) return true;
207        if (o == null || getClass() != o.getClass()) return false;
208
209        Session session = (Session) o;
210
211        if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false;
212        if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false;
213        if (mIsCompleted != session.mIsCompleted) return false;
214        if (mChildCounter != session.mChildCounter) return false;
215        if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false;
216        if (mSessionId != null ?
217                !mSessionId.equals(session.mSessionId) : session.mSessionId != null)
218            return false;
219        if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName)
220                : session.mShortMethodName != null)
221            return false;
222        if (mParentSession != null ? !mParentSession.equals(session.mParentSession)
223                : session.mParentSession != null)
224            return false;
225        if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions)
226                : session.mChildSessions != null)
227            return false;
228        return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo)
229                : session.mOwnerInfo == null;
230
231    }
232
233    @Override
234    public String toString() {
235        if (mParentSession != null && mIsStartedFromActiveSession) {
236            // Log.startSession was called from within another active session. Use the parent's
237            // Id instead of the child to reduce confusion.
238            return mParentSession.toString();
239        } else {
240            StringBuilder methodName = new StringBuilder();
241            methodName.append(mShortMethodName);
242            if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
243                methodName.append("(InCall package: ");
244                methodName.append(mOwnerInfo);
245                methodName.append(")");
246            }
247            return methodName.toString() + "@" + getFullSessionId();
248        }
249    }
250}
251