1 /*
2  * Copyright (C) 2015 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.annotation.NonNull;
20 
21 import 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  */
27 public class Session {
28 
29     public static final String START_SESSION = "START_SESSION";
30     public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
31     public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
32     public static final String END_SUBSESSION = "END_SUBSESSION";
33     public static final String END_SESSION = "END_SESSION";
34 
35     public static final int UNDEFINED = -1;
36 
37     private String mSessionId;
38     private String mShortMethodName;
39     private long mExecutionStartTimeMs;
40     private long mExecutionEndTimeMs = UNDEFINED;
41     private Session mParentSession;
42     private ArrayList<Session> mChildSessions;
43     private boolean mIsCompleted = false;
44     private int mChildCounter = 0;
45     // True if this is a subsession that has been started from the same thread as the parent
46     // session. This can happen if Log.startSession(...) is called multiple times on the same
47     // thread in the case of one Telecom entry point method calling another entry point method.
48     // In this case, we can just make this subsession "invisible," but still keep track of it so
49     // that the Log.endSession() calls match up.
50     private boolean mIsStartedFromActiveSession = false;
51     // Optionally provided info about the method/class/component that started the session in order
52     // to make Logging easier. This info will be provided in parentheses along with the session.
53     private String mOwnerInfo;
54 
Session(String sessionId, String shortMethodName, long startTimeMs, long threadID, boolean isStartedFromActiveSession, String ownerInfo)55     public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID,
56             boolean isStartedFromActiveSession, String ownerInfo) {
57         setSessionId(sessionId);
58         setShortMethodName(shortMethodName);
59         mExecutionStartTimeMs = startTimeMs;
60         mParentSession = null;
61         mChildSessions = new ArrayList<>(5);
62         mIsStartedFromActiveSession = isStartedFromActiveSession;
63         mOwnerInfo = ownerInfo;
64     }
65 
setSessionId(@onNull String sessionId)66     public void setSessionId(@NonNull String sessionId) {
67        if(sessionId == null) {
68            mSessionId = "?";
69        }
70        mSessionId = sessionId;
71     }
72 
getShortMethodName()73     public String getShortMethodName() {
74         return mShortMethodName;
75     }
76 
setShortMethodName(String shortMethodName)77     public void setShortMethodName(String shortMethodName) {
78         if(shortMethodName == null) {
79             shortMethodName = "";
80         }
81         mShortMethodName = shortMethodName;
82     }
83 
setParentSession(Session parentSession)84     public void setParentSession(Session parentSession) {
85         mParentSession = parentSession;
86     }
87 
addChild(Session childSession)88     public void addChild(Session childSession) {
89         if(childSession != null) {
90             mChildSessions.add(childSession);
91         }
92     }
93 
removeChild(Session child)94     public void removeChild(Session child) {
95         if(child != null) {
96             mChildSessions.remove(child);
97         }
98     }
99 
getExecutionStartTimeMilliseconds()100     public long getExecutionStartTimeMilliseconds() {
101         return mExecutionStartTimeMs;
102     }
103 
setExecutionStartTimeMs(long startTimeMs)104     public void setExecutionStartTimeMs(long startTimeMs) {
105         mExecutionStartTimeMs = startTimeMs;
106     }
107 
getParentSession()108     public Session getParentSession() {
109         return mParentSession;
110     }
111 
getChildSessions()112     public ArrayList<Session> getChildSessions() {
113         return mChildSessions;
114     }
115 
isSessionCompleted()116     public boolean isSessionCompleted() {
117         return mIsCompleted;
118     }
119 
isStartedFromActiveSession()120     public boolean isStartedFromActiveSession() {
121         return mIsStartedFromActiveSession;
122     }
123 
124     // Mark this session complete. This will be deleted by Log when all subsessions are complete
125     // as well.
markSessionCompleted(long executionEndTimeMs)126     public void markSessionCompleted(long executionEndTimeMs) {
127         mExecutionEndTimeMs = executionEndTimeMs;
128         mIsCompleted = true;
129     }
130 
getLocalExecutionTime()131     public long getLocalExecutionTime() {
132         if(mExecutionEndTimeMs == UNDEFINED) {
133             return UNDEFINED;
134         }
135         return mExecutionEndTimeMs - mExecutionStartTimeMs;
136     }
137 
getNextChildId()138     public synchronized String getNextChildId() {
139         return String.valueOf(mChildCounter++);
140     }
141 
142     @Override
equals(Object obj)143     public boolean equals(Object obj) {
144         if (!(obj instanceof Session)) {
145             return false;
146         }
147         if (obj == this) {
148             return true;
149         }
150         Session otherSession = (Session) obj;
151         return (mSessionId.equals(otherSession.mSessionId)) &&
152                 (mShortMethodName.equals(otherSession.mShortMethodName)) &&
153                 mExecutionStartTimeMs == otherSession.mExecutionStartTimeMs &&
154                 mParentSession == otherSession.mParentSession &&
155                 mChildSessions.equals(otherSession.mChildSessions) &&
156                 mIsCompleted == otherSession.mIsCompleted &&
157                 mExecutionEndTimeMs == otherSession.mExecutionEndTimeMs &&
158                 mChildCounter == otherSession.mChildCounter &&
159                 mIsStartedFromActiveSession == otherSession.mIsStartedFromActiveSession &&
160                 mOwnerInfo == otherSession.mOwnerInfo;
161     }
162 
163     // Builds full session id recursively
getFullSessionId()164     private String getFullSessionId() {
165         // Cache mParentSession locally to prevent a concurrency problem where
166         // Log.endParentSessions() is called while a logging statement is running (Log.i, for
167         // example) and setting mParentSession to null in a different thread after the null check
168         // occurred.
169         Session parentSession = mParentSession;
170         if(parentSession == null) {
171             return mSessionId;
172         } else {
173             return parentSession.getFullSessionId() + "_" + mSessionId;
174         }
175     }
176 
177     // Print out the full Session tree from any subsession node
printFullSessionTree()178     public String printFullSessionTree() {
179         // Get to the top of the tree
180         Session topNode = this;
181         while(topNode.getParentSession() != null) {
182             topNode = topNode.getParentSession();
183         }
184         return topNode.printSessionTree();
185     }
186 
187     // Recursively move down session tree using DFS, but print out each node when it is reached.
printSessionTree()188     public String printSessionTree() {
189         StringBuilder sb = new StringBuilder();
190         printSessionTree(0, sb);
191         return sb.toString();
192     }
193 
printSessionTree(int tabI, StringBuilder sb)194     private void printSessionTree(int tabI, StringBuilder sb) {
195         sb.append(toString());
196         for (Session child : mChildSessions) {
197             sb.append("\n");
198             for(int i = 0; i <= tabI; i++) {
199                 sb.append("\t");
200             }
201             child.printSessionTree(tabI + 1, sb);
202         }
203     }
204 
205     @Override
toString()206     public String toString() {
207         if(mParentSession != null && mIsStartedFromActiveSession) {
208             // Log.startSession was called from within another active session. Use the parent's
209             // Id instead of the child to reduce confusion.
210             return mParentSession.toString();
211         } else {
212             StringBuilder methodName = new StringBuilder();
213             methodName.append(mShortMethodName);
214             if(mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
215                 methodName.append("(InCall package: ");
216                 methodName.append(mOwnerInfo);
217                 methodName.append(")");
218             }
219             return methodName.toString() + "@" + getFullSessionId();
220         }
221     }
222 }
223