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 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 66 public void setSessionId(@NonNull String sessionId) { 67 if(sessionId == null) { 68 mSessionId = "?"; 69 } 70 mSessionId = sessionId; 71 } 72 73 public String getShortMethodName() { 74 return mShortMethodName; 75 } 76 77 public void setShortMethodName(String shortMethodName) { 78 if(shortMethodName == null) { 79 shortMethodName = ""; 80 } 81 mShortMethodName = shortMethodName; 82 } 83 84 public void setParentSession(Session parentSession) { 85 mParentSession = parentSession; 86 } 87 88 public void addChild(Session childSession) { 89 if(childSession != null) { 90 mChildSessions.add(childSession); 91 } 92 } 93 94 public void removeChild(Session child) { 95 if(child != null) { 96 mChildSessions.remove(child); 97 } 98 } 99 100 public long getExecutionStartTimeMilliseconds() { 101 return mExecutionStartTimeMs; 102 } 103 104 public void setExecutionStartTimeMs(long startTimeMs) { 105 mExecutionStartTimeMs = startTimeMs; 106 } 107 108 public Session getParentSession() { 109 return mParentSession; 110 } 111 112 public ArrayList<Session> getChildSessions() { 113 return mChildSessions; 114 } 115 116 public boolean isSessionCompleted() { 117 return mIsCompleted; 118 } 119 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. 126 public void markSessionCompleted(long executionEndTimeMs) { 127 mExecutionEndTimeMs = executionEndTimeMs; 128 mIsCompleted = true; 129 } 130 131 public long getLocalExecutionTime() { 132 if(mExecutionEndTimeMs == UNDEFINED) { 133 return UNDEFINED; 134 } 135 return mExecutionEndTimeMs - mExecutionStartTimeMs; 136 } 137 138 public synchronized String getNextChildId() { 139 return String.valueOf(mChildCounter++); 140 } 141 142 @Override 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 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 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. 188 public String printSessionTree() { 189 StringBuilder sb = new StringBuilder(); 190 printSessionTree(0, sb); 191 return sb.toString(); 192 } 193 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 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