/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.telecom; import android.annotation.NonNull; import java.util.ArrayList; /** * The session that stores information about a thread's point of entry into the Telecom code that * persists until the thread exits Telecom. */ public class Session { public static final String START_SESSION = "START_SESSION"; public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION"; public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION"; public static final String END_SUBSESSION = "END_SUBSESSION"; public static final String END_SESSION = "END_SESSION"; public static final int UNDEFINED = -1; private String mSessionId; private String mShortMethodName; private long mExecutionStartTimeMs; private long mExecutionEndTimeMs = UNDEFINED; private Session mParentSession; private ArrayList mChildSessions; private boolean mIsCompleted = false; private int mChildCounter = 0; // True if this is a subsession that has been started from the same thread as the parent // session. This can happen if Log.startSession(...) is called multiple times on the same // thread in the case of one Telecom entry point method calling another entry point method. // In this case, we can just make this subsession "invisible," but still keep track of it so // that the Log.endSession() calls match up. private boolean mIsStartedFromActiveSession = false; // Optionally provided info about the method/class/component that started the session in order // to make Logging easier. This info will be provided in parentheses along with the session. private String mOwnerInfo; public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID, boolean isStartedFromActiveSession, String ownerInfo) { setSessionId(sessionId); setShortMethodName(shortMethodName); mExecutionStartTimeMs = startTimeMs; mParentSession = null; mChildSessions = new ArrayList<>(5); mIsStartedFromActiveSession = isStartedFromActiveSession; mOwnerInfo = ownerInfo; } public void setSessionId(@NonNull String sessionId) { if(sessionId == null) { mSessionId = "?"; } mSessionId = sessionId; } public String getShortMethodName() { return mShortMethodName; } public void setShortMethodName(String shortMethodName) { if(shortMethodName == null) { shortMethodName = ""; } mShortMethodName = shortMethodName; } public void setParentSession(Session parentSession) { mParentSession = parentSession; } public void addChild(Session childSession) { if(childSession != null) { mChildSessions.add(childSession); } } public void removeChild(Session child) { if(child != null) { mChildSessions.remove(child); } } public long getExecutionStartTimeMilliseconds() { return mExecutionStartTimeMs; } public void setExecutionStartTimeMs(long startTimeMs) { mExecutionStartTimeMs = startTimeMs; } public Session getParentSession() { return mParentSession; } public ArrayList getChildSessions() { return mChildSessions; } public boolean isSessionCompleted() { return mIsCompleted; } public boolean isStartedFromActiveSession() { return mIsStartedFromActiveSession; } // Mark this session complete. This will be deleted by Log when all subsessions are complete // as well. public void markSessionCompleted(long executionEndTimeMs) { mExecutionEndTimeMs = executionEndTimeMs; mIsCompleted = true; } public long getLocalExecutionTime() { if(mExecutionEndTimeMs == UNDEFINED) { return UNDEFINED; } return mExecutionEndTimeMs - mExecutionStartTimeMs; } public synchronized String getNextChildId() { return String.valueOf(mChildCounter++); } @Override public boolean equals(Object obj) { if (!(obj instanceof Session)) { return false; } if (obj == this) { return true; } Session otherSession = (Session) obj; return (mSessionId.equals(otherSession.mSessionId)) && (mShortMethodName.equals(otherSession.mShortMethodName)) && mExecutionStartTimeMs == otherSession.mExecutionStartTimeMs && mParentSession == otherSession.mParentSession && mChildSessions.equals(otherSession.mChildSessions) && mIsCompleted == otherSession.mIsCompleted && mExecutionEndTimeMs == otherSession.mExecutionEndTimeMs && mChildCounter == otherSession.mChildCounter && mIsStartedFromActiveSession == otherSession.mIsStartedFromActiveSession && mOwnerInfo == otherSession.mOwnerInfo; } // Builds full session id recursively private String getFullSessionId() { // Cache mParentSession locally to prevent a concurrency problem where // Log.endParentSessions() is called while a logging statement is running (Log.i, for // example) and setting mParentSession to null in a different thread after the null check // occurred. Session parentSession = mParentSession; if(parentSession == null) { return mSessionId; } else { return parentSession.getFullSessionId() + "_" + mSessionId; } } // Print out the full Session tree from any subsession node public String printFullSessionTree() { // Get to the top of the tree Session topNode = this; while(topNode.getParentSession() != null) { topNode = topNode.getParentSession(); } return topNode.printSessionTree(); } // Recursively move down session tree using DFS, but print out each node when it is reached. public String printSessionTree() { StringBuilder sb = new StringBuilder(); printSessionTree(0, sb); return sb.toString(); } private void printSessionTree(int tabI, StringBuilder sb) { sb.append(toString()); for (Session child : mChildSessions) { sb.append("\n"); for(int i = 0; i <= tabI; i++) { sb.append("\t"); } child.printSessionTree(tabI + 1, sb); } } @Override public String toString() { if(mParentSession != null && mIsStartedFromActiveSession) { // Log.startSession was called from within another active session. Use the parent's // Id instead of the child to reduce confusion. return mParentSession.toString(); } else { StringBuilder methodName = new StringBuilder(); methodName.append(mShortMethodName); if(mOwnerInfo != null && !mOwnerInfo.isEmpty()) { methodName.append("(InCall package: "); methodName.append(mOwnerInfo); methodName.append(")"); } return methodName.toString() + "@" + getFullSessionId(); } } }