blob: 50c3cd97d77a0fb6486f254cd6a73ff0da18f9e1 [file] [log] [blame]
Brad Ebinger51b98342016-09-22 16:30:46 -07001/*
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;
Brad Ebingera0dc9762016-10-21 09:41:29 -070020import android.os.Parcel;
21import android.os.Parcelable;
Brad Ebinger0c3541b2016-11-01 14:11:38 -070022import android.telecom.Log;
Brad Ebingera0dc9762016-10-21 09:41:29 -070023import android.text.TextUtils;
24
25import com.android.internal.annotations.VisibleForTesting;
Brad Ebinger51b98342016-09-22 16:30:46 -070026
27import java.util.ArrayList;
28
29/**
Brad Ebinger0c3541b2016-11-01 14:11:38 -070030 * Stores information about a thread's point of entry into that should persist until that thread
31 * exits.
Brad Ebinger51b98342016-09-22 16:30:46 -070032 * @hide
33 */
34public class Session {
35
36 public static final String START_SESSION = "START_SESSION";
Brad Ebinger0c3541b2016-11-01 14:11:38 -070037 public static final String START_EXTERNAL_SESSION = "START_EXTERNAL_SESSION";
Brad Ebinger51b98342016-09-22 16:30:46 -070038 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
Brad Ebingera0dc9762016-10-21 09:41:29 -070043 public static final String SUBSESSION_SEPARATION_CHAR = "->";
Brad Ebinger0c3541b2016-11-01 14:11:38 -070044 public static final String SESSION_SEPARATION_CHAR_CHILD = "_";
Brad Ebingera0dc9762016-10-21 09:41:29 -070045 public static final String EXTERNAL_INDICATOR = "E-";
Brad Ebinger0c3541b2016-11-01 14:11:38 -070046 public static final String TRUNCATE_STRING = "...";
Brad Ebingera0dc9762016-10-21 09:41:29 -070047
Brad Ebinger096d2822016-10-13 15:26:58 -070048 /**
49 * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()}
50 * if the Session is canceled.
51 */
Brad Ebinger51b98342016-09-22 16:30:46 -070052 public static final int UNDEFINED = -1;
53
Brad Ebingera0dc9762016-10-21 09:41:29 -070054 public static class Info implements Parcelable {
55 public final String sessionId;
Brad Ebinger0c3541b2016-11-01 14:11:38 -070056 public final String methodPath;
Brad Ebingera0dc9762016-10-21 09:41:29 -070057
Brad Ebinger0c3541b2016-11-01 14:11:38 -070058 private Info(String id, String path) {
Brad Ebingera0dc9762016-10-21 09:41:29 -070059 sessionId = id;
Brad Ebinger0c3541b2016-11-01 14:11:38 -070060 methodPath = path;
Brad Ebingera0dc9762016-10-21 09:41:29 -070061 }
62
63 public static Info getInfo (Session s) {
Brad Ebinger0c3541b2016-11-01 14:11:38 -070064 // 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()));
Brad Ebingera0dc9762016-10-21 09:41:29 -070069 }
70
71 /** Responsible for creating Info objects for deserialized Parcels. */
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -070072 public static final @android.annotation.NonNull Parcelable.Creator<Info> CREATOR =
Brad Ebingera0dc9762016-10-21 09:41:29 -070073 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
89 public int describeContents() {
90 return 0;
91 }
92
93 /** Writes Info object into a Parcel. */
94 @Override
95 public void writeToParcel(Parcel destination, int flags) {
96 destination.writeString(sessionId);
Brad Ebinger0c3541b2016-11-01 14:11:38 -070097 destination.writeString(methodPath);
Brad Ebingera0dc9762016-10-21 09:41:29 -070098 }
99 }
100
Brad Ebinger51b98342016-09-22 16:30:46 -0700101 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;
Brad Ebingera0dc9762016-10-21 09:41:29 -0700108 private boolean mIsExternal = false;
Brad Ebinger51b98342016-09-22 16:30:46 -0700109 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;
Brad Ebingera0dc9762016-10-21 09:41:29 -0700119 // Cache Full Method path so that recursive population of the full method path only needs to
120 // be calculated once.
121 private String mFullMethodPathCache;
Brad Ebinger51b98342016-09-22 16:30:46 -0700122
Brad Ebingera0dc9762016-10-21 09:41:29 -0700123 public Session(String sessionId, String shortMethodName, long startTimeMs,
Brad Ebinger51b98342016-09-22 16:30:46 -0700124 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
134 public void setSessionId(@NonNull String sessionId) {
135 if (sessionId == null) {
136 mSessionId = "?";
137 }
138 mSessionId = sessionId;
139 }
140
141 public String getShortMethodName() {
142 return mShortMethodName;
143 }
144
145 public void setShortMethodName(String shortMethodName) {
146 if (shortMethodName == null) {
147 shortMethodName = "";
148 }
149 mShortMethodName = shortMethodName;
150 }
151
Brad Ebingera0dc9762016-10-21 09:41:29 -0700152 public void setIsExternal(boolean isExternal) {
153 mIsExternal = isExternal;
154 }
155
156 public boolean isExternal() {
157 return mIsExternal;
158 }
159
Brad Ebinger51b98342016-09-22 16:30:46 -0700160 public void setParentSession(Session parentSession) {
161 mParentSession = parentSession;
162 }
163
164 public void addChild(Session childSession) {
165 if (childSession != null) {
166 mChildSessions.add(childSession);
167 }
168 }
169
170 public void removeChild(Session child) {
171 if (child != null) {
172 mChildSessions.remove(child);
173 }
174 }
175
176 public long getExecutionStartTimeMilliseconds() {
177 return mExecutionStartTimeMs;
178 }
179
180 public void setExecutionStartTimeMs(long startTimeMs) {
181 mExecutionStartTimeMs = startTimeMs;
182 }
183
184 public Session getParentSession() {
185 return mParentSession;
186 }
187
188 public ArrayList<Session> getChildSessions() {
189 return mChildSessions;
190 }
191
192 public boolean isSessionCompleted() {
193 return mIsCompleted;
194 }
195
196 public boolean isStartedFromActiveSession() {
197 return mIsStartedFromActiveSession;
198 }
199
Brad Ebingera0dc9762016-10-21 09:41:29 -0700200 public Info getInfo() {
201 return Info.getInfo(this);
202 }
203
204 @VisibleForTesting
205 public String getSessionId() {
206 return mSessionId;
207 }
208
Brad Ebinger51b98342016-09-22 16:30:46 -0700209 // Mark this session complete. This will be deleted by Log when all subsessions are complete
210 // as well.
211 public void markSessionCompleted(long executionEndTimeMs) {
212 mExecutionEndTimeMs = executionEndTimeMs;
213 mIsCompleted = true;
214 }
215
216 public long getLocalExecutionTime() {
217 if (mExecutionEndTimeMs == UNDEFINED) {
218 return UNDEFINED;
219 }
220 return mExecutionEndTimeMs - mExecutionStartTimeMs;
221 }
222
223 public synchronized String getNextChildId() {
224 return String.valueOf(mChildCounter++);
225 }
226
Brad Ebinger51b98342016-09-22 16:30:46 -0700227 // Builds full session id recursively
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 {
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700237 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
Brad Ebinger51b98342016-09-22 16:30:46 -0700246 }
247 }
248
249 // Print out the full Session tree from any subsession node
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.
260 public String printSessionTree() {
261 StringBuilder sb = new StringBuilder();
262 printSessionTree(0, sb);
263 return sb.toString();
264 }
265
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
Brad Ebingera0dc9762016-10-21 09:41:29 -0700277 // Recursively concatenate mShortMethodName with the parent Sessions to create full method
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700278 // path. if truncatePath is set to true, all other external sessions (except for the most
279 // recent) will be truncated to "..."
280 public String getFullMethodPath(boolean truncatePath) {
Brad Ebingera0dc9762016-10-21 09:41:29 -0700281 StringBuilder sb = new StringBuilder();
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700282 getFullMethodPath(sb, truncatePath);
Brad Ebingera0dc9762016-10-21 09:41:29 -0700283 return sb.toString();
284 }
285
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700286 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) {
Brad Ebingera0dc9762016-10-21 09:41:29 -0700290 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);
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700299 parentSession.getFullMethodPath(sb, truncatePath);
Brad Ebingera0dc9762016-10-21 09:41:29 -0700300 sb.append(SUBSESSION_SEPARATION_CHAR);
301 }
Brad Ebinger3445f822016-10-24 16:40:49 -0700302 // Encapsulate the external session's method name so it is obvious what part of the session
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700303 // is external or truncate it if we do not want the entire history.
Brad Ebinger3445f822016-10-24 16:40:49 -0700304 if (isExternal()) {
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700305 if (truncatePath) {
306 sb.append(TRUNCATE_STRING);
307 } else {
308 sb.append("(");
309 sb.append(mShortMethodName);
310 sb.append(")");
311 }
Brad Ebinger3445f822016-10-24 16:40:49 -0700312 } else {
313 sb.append(mShortMethodName);
314 }
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700315 // If we are returning the truncated path, do not save that path as the full path.
316 if (isSessionStarted && !truncatePath) {
Brad Ebingera0dc9762016-10-21 09:41:29 -0700317 // 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 }
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700322 // Recursively move to the top of the tree to see if the parent session is external.
323 private boolean isSessionExternal() {
324 if (getParentSession() == null) {
325 return isExternal();
326 } else {
327 return getParentSession().isSessionExternal();
328 }
329 }
Brad Ebingera0dc9762016-10-21 09:41:29 -0700330
Brad Ebinger51b98342016-09-22 16:30:46 -0700331 @Override
Brad Ebinger096d2822016-10-13 15:26:58 -0700332 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
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
Brad Ebinger51b98342016-09-22 16:30:46 -0700376 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();
Brad Ebinger0c3541b2016-11-01 14:11:38 -0700383 methodName.append(getFullMethodPath(false /*truncatePath*/));
Brad Ebinger51b98342016-09-22 16:30:46 -0700384 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}