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
17package com.android.server.telecom;
18
19import android.annotation.NonNull;
20
21import 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 */
27public 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