Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 20 | import android.os.Parcel; |
| 21 | import android.os.Parcelable; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 22 | import android.telecom.Log; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 23 | import android.text.TextUtils; |
| 24 | |
| 25 | import com.android.internal.annotations.VisibleForTesting; |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 26 | |
| 27 | import java.util.ArrayList; |
| 28 | |
| 29 | /** |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 30 | * Stores information about a thread's point of entry into that should persist until that thread |
| 31 | * exits. |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 32 | * @hide |
| 33 | */ |
| 34 | public class Session { |
| 35 | |
| 36 | public static final String START_SESSION = "START_SESSION"; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 37 | public static final String START_EXTERNAL_SESSION = "START_EXTERNAL_SESSION"; |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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 | |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 43 | public static final String SUBSESSION_SEPARATION_CHAR = "->"; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 44 | public static final String SESSION_SEPARATION_CHAR_CHILD = "_"; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 45 | public static final String EXTERNAL_INDICATOR = "E-"; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 46 | public static final String TRUNCATE_STRING = "..."; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 47 | |
Brad Ebinger | 096d282 | 2016-10-13 15:26:58 -0700 | [diff] [blame] | 48 | /** |
| 49 | * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()} |
| 50 | * if the Session is canceled. |
| 51 | */ |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 52 | public static final int UNDEFINED = -1; |
| 53 | |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 54 | public static class Info implements Parcelable { |
| 55 | public final String sessionId; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 56 | public final String methodPath; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 57 | |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 58 | private Info(String id, String path) { |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 59 | sessionId = id; |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 60 | methodPath = path; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | public static Info getInfo (Session s) { |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 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())); |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 69 | } |
| 70 | |
| 71 | /** Responsible for creating Info objects for deserialized Parcels. */ |
Jeff Sharkey | 9e8f83d | 2019-02-28 12:06:45 -0700 | [diff] [blame^] | 72 | public static final @android.annotation.NonNull Parcelable.Creator<Info> CREATOR = |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 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 |
| 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 Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 97 | destination.writeString(methodPath); |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 98 | } |
| 99 | } |
| 100 | |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 108 | private boolean mIsExternal = false; |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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; |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 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; |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 122 | |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 123 | public Session(String sessionId, String shortMethodName, long startTimeMs, |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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 | |
| 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 Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 152 | public void setIsExternal(boolean isExternal) { |
| 153 | mIsExternal = isExternal; |
| 154 | } |
| 155 | |
| 156 | public boolean isExternal() { |
| 157 | return mIsExternal; |
| 158 | } |
| 159 | |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 160 | 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 Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 200 | public Info getInfo() { |
| 201 | return Info.getInfo(this); |
| 202 | } |
| 203 | |
| 204 | @VisibleForTesting |
| 205 | public String getSessionId() { |
| 206 | return mSessionId; |
| 207 | } |
| 208 | |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 209 | // 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 Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 227 | // 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 Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 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 | |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 246 | } |
| 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 Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 277 | // Recursively concatenate mShortMethodName with the parent Sessions to create full method |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 278 | // 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 Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 281 | StringBuilder sb = new StringBuilder(); |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 282 | getFullMethodPath(sb, truncatePath); |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 283 | return sb.toString(); |
| 284 | } |
| 285 | |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 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) { |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 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); |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 299 | parentSession.getFullMethodPath(sb, truncatePath); |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 300 | sb.append(SUBSESSION_SEPARATION_CHAR); |
| 301 | } |
Brad Ebinger | 3445f82 | 2016-10-24 16:40:49 -0700 | [diff] [blame] | 302 | // Encapsulate the external session's method name so it is obvious what part of the session |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 303 | // is external or truncate it if we do not want the entire history. |
Brad Ebinger | 3445f82 | 2016-10-24 16:40:49 -0700 | [diff] [blame] | 304 | if (isExternal()) { |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 305 | if (truncatePath) { |
| 306 | sb.append(TRUNCATE_STRING); |
| 307 | } else { |
| 308 | sb.append("("); |
| 309 | sb.append(mShortMethodName); |
| 310 | sb.append(")"); |
| 311 | } |
Brad Ebinger | 3445f82 | 2016-10-24 16:40:49 -0700 | [diff] [blame] | 312 | } else { |
| 313 | sb.append(mShortMethodName); |
| 314 | } |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 315 | // If we are returning the truncated path, do not save that path as the full path. |
| 316 | if (isSessionStarted && !truncatePath) { |
Brad Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 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 | } |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 322 | // 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 Ebinger | a0dc976 | 2016-10-21 09:41:29 -0700 | [diff] [blame] | 330 | |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 331 | @Override |
Brad Ebinger | 096d282 | 2016-10-13 15:26:58 -0700 | [diff] [blame] | 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 |
| 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 Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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(); |
Brad Ebinger | 0c3541b | 2016-11-01 14:11:38 -0700 | [diff] [blame] | 383 | methodName.append(getFullMethodPath(false /*truncatePath*/)); |
Brad Ebinger | 51b9834 | 2016-09-22 16:30:46 -0700 | [diff] [blame] | 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 | } |