• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.telecom.Logging;
18 
19 import android.annotation.NonNull;
20 import android.os.Parcel;
21 import android.os.Parcelable;
22 import android.telecom.Log;
23 import android.text.TextUtils;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import java.util.ArrayList;
28 
29 /**
30  * Stores information about a thread's point of entry into that should persist until that thread
31  * exits.
32  * @hide
33  */
34 public class Session {
35 
36     public static final String START_SESSION = "START_SESSION";
37     public static final String START_EXTERNAL_SESSION = "START_EXTERNAL_SESSION";
38     public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
39     public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
40     public static final String END_SUBSESSION = "END_SUBSESSION";
41     public static final String END_SESSION = "END_SESSION";
42 
43     public static final String SUBSESSION_SEPARATION_CHAR = "->";
44     public static final String SESSION_SEPARATION_CHAR_CHILD = "_";
45     public static final String EXTERNAL_INDICATOR = "E-";
46     public static final String TRUNCATE_STRING = "...";
47 
48     /**
49      * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
50      * if the Session is canceled.
51      */
52     public static final int UNDEFINED = -1;
53 
54     public static class Info implements Parcelable {
55         public final String sessionId;
56         public final String methodPath;
57 
Info(String id, String path)58         private Info(String id, String path) {
59             sessionId = id;
60             methodPath = path;
61         }
62 
getInfo(Session s)63         public static Info getInfo (Session s) {
64             // Create Info based on the truncated method path if the session is external, so we do
65             // not get multiple stacking external sessions (unless we have DEBUG level logging or
66             // lower).
67             return new Info(s.getFullSessionId(), s.getFullMethodPath(
68                     !Log.DEBUG && s.isSessionExternal()));
69         }
70 
71         /** Responsible for creating Info objects for deserialized Parcels. */
72         public static final Parcelable.Creator<Info> CREATOR =
73                 new Parcelable.Creator<Info> () {
74                     @Override
75                     public Info createFromParcel(Parcel source) {
76                         String id = source.readString();
77                         String methodName = source.readString();
78                         return new Info(id, methodName);
79                     }
80 
81                     @Override
82                     public Info[] newArray(int size) {
83                         return new Info[size];
84                     }
85                 };
86 
87         /** {@inheritDoc} */
88         @Override
describeContents()89         public int describeContents() {
90             return 0;
91         }
92 
93         /** Writes Info object into a Parcel. */
94         @Override
writeToParcel(Parcel destination, int flags)95         public void writeToParcel(Parcel destination, int flags) {
96             destination.writeString(sessionId);
97             destination.writeString(methodPath);
98         }
99     }
100 
101     private String mSessionId;
102     private String mShortMethodName;
103     private long mExecutionStartTimeMs;
104     private long mExecutionEndTimeMs = UNDEFINED;
105     private Session mParentSession;
106     private ArrayList<Session> mChildSessions;
107     private boolean mIsCompleted = false;
108     private boolean mIsExternal = false;
109     private int mChildCounter = 0;
110     // True if this is a subsession that has been started from the same thread as the parent
111     // session. This can happen if Log.startSession(...) is called multiple times on the same
112     // thread in the case of one Telecom entry point method calling another entry point method.
113     // In this case, we can just make this subsession "invisible," but still keep track of it so
114     // that the Log.endSession() calls match up.
115     private boolean mIsStartedFromActiveSession = false;
116     // Optionally provided info about the method/class/component that started the session in order
117     // to make Logging easier. This info will be provided in parentheses along with the session.
118     private String mOwnerInfo;
119     // Cache Full Method path so that recursive population of the full method path only needs to
120     // be calculated once.
121     private String mFullMethodPathCache;
122 
Session(String sessionId, String shortMethodName, long startTimeMs, boolean isStartedFromActiveSession, String ownerInfo)123     public Session(String sessionId, String shortMethodName, long startTimeMs,
124             boolean isStartedFromActiveSession, String ownerInfo) {
125         setSessionId(sessionId);
126         setShortMethodName(shortMethodName);
127         mExecutionStartTimeMs = startTimeMs;
128         mParentSession = null;
129         mChildSessions = new ArrayList<>(5);
130         mIsStartedFromActiveSession = isStartedFromActiveSession;
131         mOwnerInfo = ownerInfo;
132     }
133 
setSessionId(@onNull String sessionId)134     public void setSessionId(@NonNull String sessionId) {
135         if (sessionId == null) {
136             mSessionId = "?";
137         }
138         mSessionId = sessionId;
139     }
140 
getShortMethodName()141     public String getShortMethodName() {
142         return mShortMethodName;
143     }
144 
setShortMethodName(String shortMethodName)145     public void setShortMethodName(String shortMethodName) {
146         if (shortMethodName == null) {
147             shortMethodName = "";
148         }
149         mShortMethodName = shortMethodName;
150     }
151 
setIsExternal(boolean isExternal)152     public void setIsExternal(boolean isExternal) {
153         mIsExternal = isExternal;
154     }
155 
isExternal()156     public boolean isExternal() {
157         return mIsExternal;
158     }
159 
setParentSession(Session parentSession)160     public void setParentSession(Session parentSession) {
161         mParentSession = parentSession;
162     }
163 
addChild(Session childSession)164     public void addChild(Session childSession) {
165         if (childSession != null) {
166             mChildSessions.add(childSession);
167         }
168     }
169 
removeChild(Session child)170     public void removeChild(Session child) {
171         if (child != null) {
172             mChildSessions.remove(child);
173         }
174     }
175 
getExecutionStartTimeMilliseconds()176     public long getExecutionStartTimeMilliseconds() {
177         return mExecutionStartTimeMs;
178     }
179 
setExecutionStartTimeMs(long startTimeMs)180     public void setExecutionStartTimeMs(long startTimeMs) {
181         mExecutionStartTimeMs = startTimeMs;
182     }
183 
getParentSession()184     public Session getParentSession() {
185         return mParentSession;
186     }
187 
getChildSessions()188     public ArrayList<Session> getChildSessions() {
189         return mChildSessions;
190     }
191 
isSessionCompleted()192     public boolean isSessionCompleted() {
193         return mIsCompleted;
194     }
195 
isStartedFromActiveSession()196     public boolean isStartedFromActiveSession() {
197         return mIsStartedFromActiveSession;
198     }
199 
getInfo()200     public Info getInfo() {
201         return Info.getInfo(this);
202     }
203 
204     @VisibleForTesting
getSessionId()205     public String getSessionId() {
206         return mSessionId;
207     }
208 
209     // Mark this session complete. This will be deleted by Log when all subsessions are complete
210     // as well.
markSessionCompleted(long executionEndTimeMs)211     public void markSessionCompleted(long executionEndTimeMs) {
212         mExecutionEndTimeMs = executionEndTimeMs;
213         mIsCompleted = true;
214     }
215 
getLocalExecutionTime()216     public long getLocalExecutionTime() {
217         if (mExecutionEndTimeMs == UNDEFINED) {
218             return UNDEFINED;
219         }
220         return mExecutionEndTimeMs - mExecutionStartTimeMs;
221     }
222 
getNextChildId()223     public synchronized String getNextChildId() {
224         return String.valueOf(mChildCounter++);
225     }
226 
227     // Builds full session id recursively
getFullSessionId()228     private String getFullSessionId() {
229         // Cache mParentSession locally to prevent a concurrency problem where
230         // Log.endParentSessions() is called while a logging statement is running (Log.i, for
231         // example) and setting mParentSession to null in a different thread after the null check
232         // occurred.
233         Session parentSession = mParentSession;
234         if (parentSession == null) {
235             return mSessionId;
236         } else {
237             if (Log.VERBOSE) {
238                 return parentSession.getFullSessionId() +
239                         // Append "_X" to subsession to show subsession designation.
240                         SESSION_SEPARATION_CHAR_CHILD + mSessionId;
241             } else {
242                 // Only worry about the base ID at the top of the tree.
243                 return parentSession.getFullSessionId();
244             }
245 
246         }
247     }
248 
249     // Print out the full Session tree from any subsession node
printFullSessionTree()250     public String printFullSessionTree() {
251         // Get to the top of the tree
252         Session topNode = this;
253         while (topNode.getParentSession() != null) {
254             topNode = topNode.getParentSession();
255         }
256         return topNode.printSessionTree();
257     }
258 
259     // Recursively move down session tree using DFS, but print out each node when it is reached.
printSessionTree()260     public String printSessionTree() {
261         StringBuilder sb = new StringBuilder();
262         printSessionTree(0, sb);
263         return sb.toString();
264     }
265 
printSessionTree(int tabI, StringBuilder sb)266     private void printSessionTree(int tabI, StringBuilder sb) {
267         sb.append(toString());
268         for (Session child : mChildSessions) {
269             sb.append("\n");
270             for (int i = 0; i <= tabI; i++) {
271                 sb.append("\t");
272             }
273             child.printSessionTree(tabI + 1, sb);
274         }
275     }
276 
277     // Recursively concatenate mShortMethodName with the parent Sessions to create full method
278     // path. if truncatePath is set to true, all other external sessions (except for the most
279     // recent) will be truncated to "..."
getFullMethodPath(boolean truncatePath)280     public String getFullMethodPath(boolean truncatePath) {
281         StringBuilder sb = new StringBuilder();
282         getFullMethodPath(sb, truncatePath);
283         return sb.toString();
284     }
285 
getFullMethodPath(StringBuilder sb, boolean truncatePath)286     private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath) {
287         // Return cached value for method path. When returning the truncated path, recalculate the
288         // full path without using the cached value.
289         if (!TextUtils.isEmpty(mFullMethodPathCache) && !truncatePath) {
290             sb.append(mFullMethodPathCache);
291             return;
292         }
293         Session parentSession = getParentSession();
294         boolean isSessionStarted = false;
295         if (parentSession != null) {
296             // Check to see if the session has been renamed yet. If it has not, then the session
297             // has not been continued.
298             isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName);
299             parentSession.getFullMethodPath(sb, truncatePath);
300             sb.append(SUBSESSION_SEPARATION_CHAR);
301         }
302         // Encapsulate the external session's method name so it is obvious what part of the session
303         // is external or truncate it if we do not want the entire history.
304         if (isExternal()) {
305             if (truncatePath) {
306                 sb.append(TRUNCATE_STRING);
307             } else {
308                 sb.append("(");
309                 sb.append(mShortMethodName);
310                 sb.append(")");
311             }
312         } else {
313             sb.append(mShortMethodName);
314         }
315         // If we are returning the truncated path, do not save that path as the full path.
316         if (isSessionStarted && !truncatePath) {
317             // Cache this value so that we do not have to do this work next time!
318             // We do not cache the value if the session being evaluated hasn't been continued yet.
319             mFullMethodPathCache = sb.toString();
320         }
321     }
322     // Recursively move to the top of the tree to see if the parent session is external.
isSessionExternal()323     private boolean isSessionExternal() {
324         if (getParentSession() == null) {
325             return isExternal();
326         } else {
327             return getParentSession().isSessionExternal();
328         }
329     }
330 
331     @Override
hashCode()332     public int hashCode() {
333         int result = mSessionId != null ? mSessionId.hashCode() : 0;
334         result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0);
335         result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32));
336         result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32));
337         result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0);
338         result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0);
339         result = 31 * result + (mIsCompleted ? 1 : 0);
340         result = 31 * result + mChildCounter;
341         result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0);
342         result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0);
343         return result;
344     }
345 
346     @Override
equals(Object o)347     public boolean equals(Object o) {
348         if (this == o) return true;
349         if (o == null || getClass() != o.getClass()) return false;
350 
351         Session session = (Session) o;
352 
353         if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false;
354         if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false;
355         if (mIsCompleted != session.mIsCompleted) return false;
356         if (mChildCounter != session.mChildCounter) return false;
357         if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false;
358         if (mSessionId != null ?
359                 !mSessionId.equals(session.mSessionId) : session.mSessionId != null)
360             return false;
361         if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName)
362                 : session.mShortMethodName != null)
363             return false;
364         if (mParentSession != null ? !mParentSession.equals(session.mParentSession)
365                 : session.mParentSession != null)
366             return false;
367         if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions)
368                 : session.mChildSessions != null)
369             return false;
370         return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo)
371                 : session.mOwnerInfo == null;
372 
373     }
374 
375     @Override
toString()376     public String toString() {
377         if (mParentSession != null && mIsStartedFromActiveSession) {
378             // Log.startSession was called from within another active session. Use the parent's
379             // Id instead of the child to reduce confusion.
380             return mParentSession.toString();
381         } else {
382             StringBuilder methodName = new StringBuilder();
383             methodName.append(getFullMethodPath(false /*truncatePath*/));
384             if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
385                 methodName.append("(InCall package: ");
386                 methodName.append(mOwnerInfo);
387                 methodName.append(")");
388             }
389             return methodName.toString() + "@" + getFullSessionId();
390         }
391     }
392 }
393