diff options
Diffstat (limited to 'telecomm')
12 files changed, 385 insertions, 120 deletions
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index a52614d5cda1..531f51604507 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -52,6 +52,8 @@ import java.util.concurrent.CopyOnWriteArrayList; * Represents an ongoing phone call that the in-call app should present to the user. */ public final class Call { + private static final String LOG_TAG = "TelecomCall"; + /** * The state of a {@code Call} when newly created. */ @@ -2912,6 +2914,11 @@ public final class Call { } } catch (BadParcelableException e) { return false; + } catch (ClassCastException e) { + Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key); + // until we know what is causing this, we should rethrow -- this is still not + // expected. + throw e; } } } diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java index a03d7e25d4c5..69dd5562711a 100644 --- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java +++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java @@ -112,7 +112,7 @@ public class CallerInfoAsyncQuery { if (DBG) Log.d(LOG_TAG, "Trying to get current content resolver..."); final int currentUser = ActivityManager.getCurrentUser(); - final int myUser = UserManager.get(context).getProcessUserId(); + final int myUser = UserHandle.myUserId(); if (DBG) Log.d(LOG_TAG, "myUser=" + myUser + "currentUser=" + currentUser); diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index f8037175fb05..7adcd4675297 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -101,7 +101,7 @@ public abstract class Conference extends Conferenceable { private Set<String> mPreviousExtraKeys; private final Object mExtrasLock = new Object(); private Uri mAddress; - private int mAddressPresentation; + private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; private int mCallDirection; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index ad7d9870ca98..29d394201f39 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2164,7 +2164,7 @@ public abstract class Connection extends Conferenceable { private CallAudioState mCallAudioState; private CallEndpoint mCallEndpoint; private Uri mAddress; - private int mAddressPresentation; + private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN; private String mCallerDisplayName; private int mCallerDisplayNamePresentation; private boolean mRingbackRequested = false; diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index a34094ce6452..98949d0c45cf 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -68,7 +68,7 @@ public class Log { // Used to synchronize singleton logging lazy initialization private static final Object sSingletonSync = new Object(); private static EventManager sEventManager; - private static SessionManager sSessionManager; + private static volatile SessionManager sSessionManager; private static Object sLock = null; /** @@ -379,6 +379,23 @@ public class Log { return sSessionManager; } + @VisibleForTesting + public static SessionManager setSessionManager(Context context, + java.lang.Runnable cleanSessionRunnable) { + // Checking for null again outside of synchronization because we only need to synchronize + // during the lazy loading of the session logger. We don't need to synchronize elsewhere. + if (sSessionManager == null) { + synchronized (sSingletonSync) { + if (sSessionManager == null) { + sSessionManager = new SessionManager(cleanSessionRunnable); + sSessionManager.setContext(context); + return sSessionManager; + } + } + } + return sSessionManager; + } + public static void setTag(String tag) { TAG = tag; DEBUG = isLoggable(android.util.Log.DEBUG); diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index e2fb6019f30a..ad0d4f4de3ae 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -16,7 +16,6 @@ package android.telecom.Logging; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -24,8 +23,13 @@ import android.telecom.Log; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Stores information about a thread's point of entry into that should persist until that thread @@ -55,7 +59,7 @@ public class Session { * Initial value of mExecutionEndTimeMs and the final value of {@link #getLocalExecutionTime()} * if the Session is canceled. */ - public static final int UNDEFINED = -1; + public static final long UNDEFINED = -1; public static class Info implements Parcelable { public final String sessionId; @@ -129,46 +133,39 @@ public class Session { } } - private String mSessionId; - private String mShortMethodName; + private final String mSessionId; + private volatile String mShortMethodName; private long mExecutionStartTimeMs; private long mExecutionEndTimeMs = UNDEFINED; - private Session mParentSession; - private ArrayList<Session> mChildSessions; + private volatile Session mParentSession; + private final ArrayList<Session> mChildSessions = new ArrayList<>(5); private boolean mIsCompleted = false; - private boolean mIsExternal = false; - private int mChildCounter = 0; + private final boolean mIsExternal; + private final AtomicInteger mChildCounter = new AtomicInteger(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; + private final boolean mIsStartedFromActiveSession; // 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; + private final String mOwnerInfo; // Cache Full Method path so that recursive population of the full method path only needs to // be calculated once. - private String mFullMethodPathCache; + private volatile String mFullMethodPathCache; public Session(String sessionId, String shortMethodName, long startTimeMs, - boolean isStartedFromActiveSession, String ownerInfo) { - setSessionId(sessionId); + boolean isStartedFromActiveSession, boolean isExternal, String ownerInfo) { + mSessionId = (sessionId != null) ? sessionId : "???"; setShortMethodName(shortMethodName); mExecutionStartTimeMs = startTimeMs; mParentSession = null; - mChildSessions = new ArrayList<>(5); mIsStartedFromActiveSession = isStartedFromActiveSession; + mIsExternal = isExternal; mOwnerInfo = ownerInfo; } - public void setSessionId(@NonNull String sessionId) { - if (sessionId == null) { - mSessionId = "?"; - } - mSessionId = sessionId; - } - public String getShortMethodName() { return mShortMethodName; } @@ -180,10 +177,6 @@ public class Session { mShortMethodName = shortMethodName; } - public void setIsExternal(boolean isExternal) { - mIsExternal = isExternal; - } - public boolean isExternal() { return mIsExternal; } @@ -193,13 +186,15 @@ public class Session { } public void addChild(Session childSession) { - if (childSession != null) { + if (childSession == null) return; + synchronized (mChildSessions) { mChildSessions.add(childSession); } } public void removeChild(Session child) { - if (child != null) { + if (child == null) return; + synchronized (mChildSessions) { mChildSessions.remove(child); } } @@ -217,7 +212,9 @@ public class Session { } public ArrayList<Session> getChildSessions() { - return mChildSessions; + synchronized (mChildSessions) { + return new ArrayList<>(mChildSessions); + } } public boolean isSessionCompleted() { @@ -259,17 +256,41 @@ public class Session { return mExecutionEndTimeMs - mExecutionStartTimeMs; } - public synchronized String getNextChildId() { - return String.valueOf(mChildCounter++); + public String getNextChildId() { + return String.valueOf(mChildCounter.getAndIncrement()); } - // Builds full session id recursively + // Builds full session ID, which incliudes the optional external indicators (E), + // base session ID, and the optional sub-session IDs (_X): @[E-]...[ID][_X][_Y]... private String getFullSessionId() { - return getFullSessionId(0); + if (!Flags.endSessionImprovements()) return getFullSessionIdRecursive(0); + int currParentCount = 0; + StringBuilder id = new StringBuilder(); + Session currSession = this; + while (currSession != null) { + Session parentSession = currSession.getParentSession(); + if (parentSession != null) { + if (currParentCount >= SESSION_RECURSION_LIMIT) { + id.insert(0, getSessionId()); + id.insert(0, TRUNCATE_STRING); + android.util.Slog.w(LOG_TAG, "getFullSessionId: Hit iteration limit!"); + return id.toString(); + } + if (Log.VERBOSE) { + id.insert(0, currSession.getSessionId()); + id.insert(0, SESSION_SEPARATION_CHAR_CHILD); + } + } else { + id.insert(0, currSession.getSessionId()); + } + currSession = parentSession; + currParentCount++; + } + return id.toString(); } // keep track of calls and bail if we hit the recursion limit - private String getFullSessionId(int parentCount) { + private String getFullSessionIdRecursive(int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit @@ -286,12 +307,12 @@ public class Session { return mSessionId; } else { if (Log.VERBOSE) { - return parentSession.getFullSessionId(parentCount + 1) + return parentSession.getFullSessionIdRecursive(parentCount + 1) // Append "_X" to subsession to show subsession designation. + SESSION_SEPARATION_CHAR_CHILD + mSessionId; } else { // Only worry about the base ID at the top of the tree. - return parentSession.getFullSessionId(parentCount + 1); + return parentSession.getFullSessionIdRecursive(parentCount + 1); } } @@ -300,16 +321,18 @@ public class Session { private Session getRootSession(String callingMethod) { int currParentCount = 0; Session topNode = this; - while (topNode.getParentSession() != null) { + Session parentNode = topNode.getParentSession(); + while (parentNode != null) { if (currParentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it // will try to add session information to this logging statement, which will cause // it to hit this condition again and so on... - android.util.Slog.w(LOG_TAG, "getRootSession: Hit recursion limit from " + android.util.Slog.w(LOG_TAG, "getRootSession: Hit iteration limit from " + callingMethod); break; } - topNode = topNode.getParentSession(); + topNode = parentNode; + parentNode = topNode.getParentSession(); currParentCount++; } return topNode; @@ -320,14 +343,40 @@ public class Session { return getRootSession("printFullSessionTree").printSessionTree(); } - // Recursively move down session tree using DFS, but print out each node when it is reached. private String printSessionTree() { StringBuilder sb = new StringBuilder(); - printSessionTree(0, sb, 0); + if (!Flags.endSessionImprovements()) { + printSessionTreeRecursive(0, sb, 0); + return sb.toString(); + } + int depth = 0; + ArrayDeque<Session> deque = new ArrayDeque<>(); + deque.add(this); + while (!deque.isEmpty()) { + Session node = deque.pollFirst(); + sb.append("\t".repeat(depth)); + sb.append(node.toString()); + sb.append("\n"); + if (depth >= SESSION_RECURSION_LIMIT) { + sb.append(TRUNCATE_STRING); + depth -= 1; + continue; + } + List<Session> childSessions = node.getChildSessions().reversed(); + if (!childSessions.isEmpty()) { + depth += 1; + for (Session child : childSessions) { + deque.addFirst(child); + } + } else { + depth -= 1; + } + } return sb.toString(); } - private void printSessionTree(int tabI, StringBuilder sb, int currChildCount) { + // Recursively move down session tree using DFS, but print out each node when it is reached. + private void printSessionTreeRecursive(int tabI, StringBuilder sb, int currChildCount) { // Prevent infinite recursion. if (currChildCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will @@ -343,26 +392,85 @@ public class Session { for (int i = 0; i <= tabI; i++) { sb.append("\t"); } - child.printSessionTree(tabI + 1, sb, currChildCount + 1); + child.printSessionTreeRecursive(tabI + 1, sb, currChildCount + 1); } } - // Recursively concatenate mShortMethodName with the parent Sessions to create full method - // path. if truncatePath is set to true, all other external sessions (except for the most - // recent) will be truncated to "..." + // + + /** + * Concatenate the short method name with the parent Sessions to create full method path. + * @param truncatePath if truncatePath is set to true, all other external sessions (except for + * the most recent) will be truncated to "..." + * @return The full method path associated with this Session. + */ + @VisibleForTesting public String getFullMethodPath(boolean truncatePath) { StringBuilder sb = new StringBuilder(); - getFullMethodPath(sb, truncatePath, 0); + if (!Flags.endSessionImprovements()) { + getFullMethodPathRecursive(sb, truncatePath, 0); + return sb.toString(); + } + // Check to see if the session has been renamed yet. If it has not, then the session + // has not been continued. + Session parentSession = getParentSession(); + boolean isSessionStarted = parentSession == null + || !getShortMethodName().equals(parentSession.getShortMethodName()); + int depth = 0; + Session currSession = this; + while (currSession != null) { + String cache = currSession.mFullMethodPathCache; + // Return cached value for method path. When returning the truncated path, recalculate + // the full path without using the cached value. + if (!TextUtils.isEmpty(cache) && !truncatePath) { + sb.insert(0, cache); + return sb.toString(); + } + + parentSession = currSession.getParentSession(); + // Encapsulate the external session's method name so it is obvious what part of the + // session is external or truncate it if we do not want the entire history. + if (currSession.isExternal()) { + if (truncatePath) { + sb.insert(0, TRUNCATE_STRING); + } else { + sb.insert(0, ")"); + sb.insert(0, currSession.getShortMethodName()); + sb.insert(0, "("); + } + } else { + sb.insert(0, currSession.getShortMethodName()); + } + if (parentSession != null) { + sb.insert(0, SUBSESSION_SEPARATION_CHAR); + } + + if (depth >= SESSION_RECURSION_LIMIT) { + // Don't use Telecom's Log.w here or it will cause infinite recursion because it + // will try to add session information to this logging statement, which will cause + // it to hit this condition again and so on... + android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit iteration limit!"); + sb.insert(0, TRUNCATE_STRING); + return sb.toString(); + } + currSession = parentSession; + depth++; + } + if (isSessionStarted && !truncatePath) { + // Cache the full method path for this node so that we do not need to calculate it + // again in the future. + mFullMethodPathCache = sb.toString(); + } return sb.toString(); } - private synchronized void getFullMethodPath(StringBuilder sb, boolean truncatePath, + private synchronized void getFullMethodPathRecursive(StringBuilder sb, boolean truncatePath, int parentCount) { if (parentCount >= SESSION_RECURSION_LIMIT) { // Don't use Telecom's Log.w here or it will cause infinite recursion because it will // try to add session information to this logging statement, which will cause it to hit // this condition again and so on... - android.util.Slog.w(LOG_TAG, "getFullMethodPath: Hit recursion limit!"); + android.util.Slog.w(LOG_TAG, "getFullMethodPathRecursive: Hit recursion limit!"); sb.append(TRUNCATE_STRING); return; } @@ -378,7 +486,7 @@ public class Session { // Check to see if the session has been renamed yet. If it has not, then the session // has not been continued. isSessionStarted = !mShortMethodName.equals(parentSession.mShortMethodName); - parentSession.getFullMethodPath(sb, truncatePath, parentCount + 1); + parentSession.getFullMethodPathRecursive(sb, truncatePath, parentCount + 1); sb.append(SUBSESSION_SEPARATION_CHAR); } // Encapsulate the external session's method name so it is obvious what part of the session @@ -409,14 +517,14 @@ public class Session { @Override public int hashCode() { - int result = mSessionId != null ? mSessionId.hashCode() : 0; - result = 31 * result + (mShortMethodName != null ? mShortMethodName.hashCode() : 0); - result = 31 * result + (int) (mExecutionStartTimeMs ^ (mExecutionStartTimeMs >>> 32)); - result = 31 * result + (int) (mExecutionEndTimeMs ^ (mExecutionEndTimeMs >>> 32)); + int result = mSessionId.hashCode(); + result = 31 * result + mShortMethodName.hashCode(); + result = 31 * result + Long.hashCode(mExecutionStartTimeMs); + result = 31 * result + Long.hashCode(mExecutionEndTimeMs); result = 31 * result + (mParentSession != null ? mParentSession.hashCode() : 0); - result = 31 * result + (mChildSessions != null ? mChildSessions.hashCode() : 0); + result = 31 * result + mChildSessions.hashCode(); result = 31 * result + (mIsCompleted ? 1 : 0); - result = 31 * result + mChildCounter; + result = 31 * result + mChildCounter.hashCode(); result = 31 * result + (mIsStartedFromActiveSession ? 1 : 0); result = 31 * result + (mOwnerInfo != null ? mOwnerInfo.hashCode() : 0); return result; @@ -432,23 +540,13 @@ public class Session { if (mExecutionStartTimeMs != session.mExecutionStartTimeMs) return false; if (mExecutionEndTimeMs != session.mExecutionEndTimeMs) return false; if (mIsCompleted != session.mIsCompleted) return false; - if (mChildCounter != session.mChildCounter) return false; + if (!(mChildCounter.get() == session.mChildCounter.get())) return false; if (mIsStartedFromActiveSession != session.mIsStartedFromActiveSession) return false; - if (mSessionId != null ? - !mSessionId.equals(session.mSessionId) : session.mSessionId != null) - return false; - if (mShortMethodName != null ? !mShortMethodName.equals(session.mShortMethodName) - : session.mShortMethodName != null) - return false; - if (mParentSession != null ? !mParentSession.equals(session.mParentSession) - : session.mParentSession != null) - return false; - if (mChildSessions != null ? !mChildSessions.equals(session.mChildSessions) - : session.mChildSessions != null) - return false; - return mOwnerInfo != null ? mOwnerInfo.equals(session.mOwnerInfo) - : session.mOwnerInfo == null; - + if (!Objects.equals(mSessionId, session.mSessionId)) return false; + if (!Objects.equals(mShortMethodName, session.mShortMethodName)) return false; + if (!Objects.equals(mParentSession, session.mParentSession)) return false; + if (!Objects.equals(mChildSessions, session.mChildSessions)) return false; + return Objects.equals(mOwnerInfo, session.mOwnerInfo); } @Override diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 9d17219c1ae4..ac1e69e92ec0 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -27,6 +27,7 @@ import android.telecom.Log; import android.util.Base64; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.telecom.flags.Flags; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -36,10 +37,16 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** - * TODO: Create better Sessions Documentation + * SessionManager manages the active sessions in a HashMap, which maps the active thread(s) to the + * associated {@link Session}s. + * <p> + * Note: Sessions assume that session structure modification is synchronized on this object - only + * one thread can modify the structure of any Session at one time. Printing the current session to + * the log is not synchronized because we should not clean up a session chain while printing from + * another Thread. Either the Session chain is still active and can not be cleaned up yet, or the + * Session chain has ended and we are cleaning up. * @hide */ - public class SessionManager { // Currently using 3 letters, So don't exceed 64^3 @@ -54,11 +61,9 @@ public class SessionManager { private Context mContext; @VisibleForTesting - public ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(100); - @VisibleForTesting - public java.lang.Runnable mCleanStaleSessions = () -> - cleanupStaleSessions(getSessionCleanupTimeoutMs()); - private Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); + public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64); + private final java.lang.Runnable mCleanStaleSessions; + private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); // Overridden in LogTest to skip query to ContentProvider private interface ISessionCleanupTimeoutMs { @@ -83,7 +88,7 @@ public class SessionManager { }; // Usage is synchronized on this class. - private List<ISessionListener> mSessionListeners = new ArrayList<>(); + private final List<ISessionListener> mSessionListeners = new ArrayList<>(); public interface ISessionListener { /** @@ -103,20 +108,39 @@ public class SessionManager { } public SessionManager() { + mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs()); + } + + @VisibleForTesting + public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) { + mCleanStaleSessions = cleanStaleSessionsRunnable; } private long getSessionCleanupTimeoutMs() { return mSessionCleanupTimeoutMs.get(); } - private synchronized void resetStaleSessionTimer() { - mSessionCleanupHandler.removeCallbacksAndMessages(null); + private void resetStaleSessionTimer() { + if (!Flags.endSessionImprovements()) { + resetStaleSessionTimerOld(); + return; + } // Will be null in Log Testing - if (mCleanStaleSessions != null) { - mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + if (mCleanStaleSessions == null) return; + synchronized (mSessionCleanupHandler) { + if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, + getSessionCleanupTimeoutMs()); + } } } + private synchronized void resetStaleSessionTimerOld() { + if (mCleanStaleSessions == null) return; + mSessionCleanupHandler.removeCallbacksAndMessages(null); + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + } + /** * Determines whether or not to start a new session or continue an existing session based on * the {@link Session.Info} info passed into startSession. If info is null, a new Session is @@ -147,13 +171,11 @@ public class SessionManager { Session childSession = createSubsession(true); continueSession(childSession, shortMethodName); return; - } else { - // Only Log that we are starting the parent session. - Log.d(LOGGING_TAG, Session.START_SESSION); } Session newSession = new Session(getNextSessionID(), shortMethodName, - System.currentTimeMillis(), false, callerIdentification); + System.currentTimeMillis(), false, false, callerIdentification); mSessionMapper.put(threadId, newSession); + Log.d(LOGGING_TAG, Session.START_SESSION); } /** @@ -179,17 +201,16 @@ public class SessionManager { } // Create Session from Info and add to the sessionMapper under this ID. - Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); Session externalSession = new Session(Session.EXTERNAL_INDICATOR + sessionInfo.sessionId, - sessionInfo.methodPath, System.currentTimeMillis(), - false /*isStartedFromActiveSession*/, sessionInfo.ownerInfo); - externalSession.setIsExternal(true); + sessionInfo.methodPath, System.currentTimeMillis(), false, true, + sessionInfo.ownerInfo); // Mark the external session as already completed, since we have no way of knowing when // the external session actually has completed. externalSession.markSessionCompleted(Session.UNDEFINED); // Track the external session with the SessionMapper so that we can create and continue // an active subsession based on it. mSessionMapper.put(threadId, externalSession); + Log.d(LOGGING_TAG, Session.START_EXTERNAL_SESSION); // Create a subsession from this external Session parent node Session childSession = createSubsession(); continueSession(childSession, shortMethodName); @@ -226,13 +247,12 @@ public class SessionManager { // Start execution time of the session will be overwritten in continueSession(...). Session newSubsession = new Session(threadSession.getNextChildId(), threadSession.getShortMethodName(), System.currentTimeMillis(), - isStartedFromActiveSession, threadSession.getOwnerInfo()); + isStartedFromActiveSession, false, threadSession.getOwnerInfo()); threadSession.addChild(newSubsession); newSubsession.setParentSession(threadSession); if (!isStartedFromActiveSession) { - Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + - newSubsession.toString()); + Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION); } else { Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)"); @@ -273,7 +293,7 @@ public class SessionManager { } subsession.markSessionCompleted(Session.UNDEFINED); - endParentSessions(subsession); + cleanupSessionTreeAndNotify(subsession); } /** @@ -328,7 +348,7 @@ public class SessionManager { // Remove after completed so that reference still exists for logging the end events Session parentSession = completedSession.getParentSession(); mSessionMapper.remove(threadId); - endParentSessions(completedSession); + cleanupSessionTreeAndNotify(completedSession); // If this subsession was started from a parent session using Log.startSession, return the // ThreadID back to the parent after completion. if (parentSession != null && !parentSession.isSessionCompleted() && @@ -337,8 +357,49 @@ public class SessionManager { } } + /** + * Move up the session tree and remove completed sessions until we either hit a session that was + * not completed yet or we reach the root node. Once we reach the root node, we will report the + * session times to session complete listeners. + * @param session The Session to clean up. + */ + private void cleanupSessionTreeAndNotify(Session session) { + if (session == null) return; + if (!Flags.endSessionImprovements()) { + endParentSessionsRecursive(session); + return; + } + Session currSession = session; + // Traverse upwards and unlink until we either hit the root node or a node that isn't + // complete yet. + while (currSession != null) { + if (!currSession.isSessionCompleted() || !currSession.getChildSessions().isEmpty()) { + // We will return once the active session is completed. + return; + } + Session parentSession = currSession.getParentSession(); + currSession.setParentSession(null); + // The session is either complete when we have reached the top node or we have reached + // the node where the parent is external. We only want to report the time it took to + // complete the local session, so for external nodes, report finished when the sub-node + // completes. + boolean reportSessionComplete = + (parentSession == null && !currSession.isExternal()) + || (parentSession != null && parentSession.isExternal()); + if (parentSession != null) parentSession.removeChild(currSession); + if (reportSessionComplete) { + long fullSessionTimeMs = System.currentTimeMillis() + - currSession.getExecutionStartTimeMilliseconds(); + Log.d(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + + " ms): " + currSession); + notifySessionCompleteListeners(currSession.getShortMethodName(), fullSessionTimeMs); + } + currSession = parentSession; + } + } + // Recursively deletes all complete parent sessions of the current subsession if it is a leaf. - private void endParentSessions(Session subsession) { + private void endParentSessionsRecursive(Session subsession) { // Session is not completed or not currently a leaf, so we can not remove because a child is // still running if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) { @@ -355,7 +416,7 @@ public class SessionManager { System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds(); notifySessionCompleteListeners(subsession.getShortMethodName(), fullSessionTimeMs); } - endParentSessions(parentSession); + endParentSessionsRecursive(parentSession); } else { // All of the subsessions have been completed and it is time to report on the full // running time of the session. @@ -370,8 +431,10 @@ public class SessionManager { } private void notifySessionCompleteListeners(String methodName, long sessionTimeMs) { - for (ISessionListener l : mSessionListeners) { - l.sessionComplete(methodName, sessionTimeMs); + synchronized (mSessionListeners) { + for (ISessionListener l : mSessionListeners) { + l.sessionComplete(methodName, sessionTimeMs); + } } } @@ -380,8 +443,8 @@ public class SessionManager { return currentSession != null ? currentSession.toString() : ""; } - public synchronized void registerSessionListener(ISessionListener l) { - if (l != null) { + public void registerSessionListener(ISessionListener l) { + synchronized (mSessionListeners) { mSessionListeners.add(l); } } @@ -425,25 +488,30 @@ public class SessionManager { @VisibleForTesting public synchronized void cleanupStaleSessions(long timeoutMs) { - String logMessage = "Stale Sessions Cleaned:\n"; + StringBuilder logMessage = new StringBuilder("Stale Sessions Cleaned:"); boolean isSessionsStale = false; long currentTimeMs = System.currentTimeMillis(); // Remove references that are in the Session Mapper (causing GC to occur) on - // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS. + // sessions that are lasting longer than DEFAULT_SESSION_TIMEOUT_MS. // If this occurs, then there is most likely a Session active that never had // Log.endSession called on it. for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it = mSessionMapper.entrySet().iterator(); it.hasNext(); ) { ConcurrentHashMap.Entry<Integer, Session> entry = it.next(); Session session = entry.getValue(); - if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) { + long runTime = currentTimeMs - session.getExecutionStartTimeMilliseconds(); + if (runTime > timeoutMs) { it.remove(); - logMessage += session.printFullSessionTree() + "\n"; + logMessage.append("\n"); + logMessage.append("["); + logMessage.append(runTime); + logMessage.append("ms] "); + logMessage.append(session.printFullSessionTree()); isSessionsStale = true; } } if (isSessionsStale) { - Log.w(LOGGING_TAG, logMessage); + Log.w(LOGGING_TAG, logMessage.toString()); } else { Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned..."); } diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java index a69dfb0b255f..cdb3eaf46def 100644 --- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java +++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java @@ -16,12 +16,12 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; /** @@ -255,6 +255,11 @@ public class ParcelableCallAnalytics implements Parcelable { public static final int CALLTYPE_OUTGOING = 2; // Constants for call technology + /** + * @deprecated Legacy CDMA is unsupported. + */ + @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_DEPRECATE_CDMA) + @Deprecated public static final int CDMA_PHONE = 0x1; public static final int GSM_PHONE = 0x2; public static final int IMS_PHONE = 0x4; diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java index 5f0c8d729e74..31b84ff04b85 100644 --- a/telecomm/java/android/telecom/StatusHints.java +++ b/telecomm/java/android/telecom/StatusHints.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -40,6 +41,7 @@ public final class StatusHints implements Parcelable { private final CharSequence mLabel; private Icon mIcon; private final Bundle mExtras; + private static final String TAG = StatusHints.class.getSimpleName(); /** * @hide @@ -150,17 +152,37 @@ public final class StatusHints implements Parcelable { // incompatible types. if (icon != null && (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { - String encodedUser = icon.getUri().getEncodedUserInfo(); - // If there is no encoded user, the URI is calling into the calling user space - if (encodedUser != null) { - int userId = Integer.parseInt(encodedUser); - // Do not try to save the icon if the user id isn't in the calling user space. - if (userId != callingUserHandle.getIdentifier()) return null; + int callingUserId = callingUserHandle.getIdentifier(); + int requestingUserId = getUserIdFromAuthority( + icon.getUri().getAuthority(), callingUserId); + if (callingUserId != requestingUserId) { + return null; } + } return icon; } + /** + * Derives the user id from the authority or the default user id if none could be found. + * @param auth + * @param defaultUserId + * @return The user id from the given authority. + * @hide + */ + public static int getUserIdFromAuthority(String auth, int defaultUserId) { + if (auth == null) return defaultUserId; + int end = auth.lastIndexOf('@'); + if (end == -1) return defaultUserId; + String userIdString = auth.substring(0, end); + try { + return Integer.parseInt(userIdString); + } catch (NumberFormatException e) { + Log.w(TAG, "Error parsing userId." + e); + return UserHandle.USER_NULL; + } + } + @Override public void writeToParcel(Parcel out, int flags) { out.writeCharSequence(mLabel); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index d89c9c16eb09..e65e4b05ef98 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -29,6 +29,7 @@ import android.annotation.SuppressAutoDoc; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; @@ -1886,6 +1887,34 @@ public class TelecomManager { } /** + * This test API determines the foreground service delegation state for a VoIP app that adds + * calls via {@link TelecomManager#addCall(CallAttributes, Executor, OutcomeReceiver, + * CallControlCallback, CallEventCallback)}. Foreground Service Delegation allows applications + * to operate in the background starting in Android 14 and is granted by Telecom via a request + * to the ActivityManager. + * + * @param handle of the voip app that is being checked + * @return true if the app has foreground service delegation. Otherwise, false. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_VOIP_CALL_MONITOR_REFACTOR) + @TestApi + public boolean hasForegroundServiceDelegation(@Nullable PhoneAccountHandle handle) { + ITelecomService service = getTelecomService(); + if (service != null) { + try { + return service.hasForegroundServiceDelegation(handle, mContext.getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, + "RemoteException calling ITelecomService#hasForegroundServiceDelegation.", + e); + } + } + return false; + } + + /** * Return the line 1 phone number for given phone account. * * <p>Requires Permission: @@ -2049,11 +2078,15 @@ public class TelecomManager { /** * Ends the foreground call on the device. * <p> - * If there is a ringing call, calling this method rejects the ringing call. Otherwise the + * If there is a ringing call, calling this method rejects the ringing call. Otherwise, the * foreground call is ended. * <p> * Note: this method CANNOT be used to end ongoing emergency calls and will return {@code false} * if an attempt is made to end an emergency call. + * <p> + * Note: If the foreground call on this device is self-managed, this method will only end + * the call if the caller of this method is privileged (i.e. system, shell, or root) or system + * UI. * * @return {@code true} if there is a call which will be rejected or terminated, {@code false} * otherwise. @@ -2082,6 +2115,9 @@ public class TelecomManager { * the incoming call requests. This means, for example, that an incoming call requesting * {@link VideoProfile#STATE_BIDIRECTIONAL} will be answered, accepting that state. * + * If the ringing incoming call is self-managed, this method will only accept the call if the + * caller of this method is privileged (i.e. system, shell, or root) or system UI. + * * @deprecated Companion apps for wearable devices should use the {@link InCallService} API * instead. */ diff --git a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl index eda0f5b24958..c4a3670e4f14 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomLoader.aidl @@ -24,5 +24,5 @@ import com.android.internal.telecom.IInternalServiceRetriever; * Allows the TelecomLoaderService to pass additional dependencies required for creation. */ interface ITelecomLoader { - ITelecomService createTelecomService(IInternalServiceRetriever retriever); + ITelecomService createTelecomService(IInternalServiceRetriever retriever, String sysUiName); } diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index c85374e0b660..6fb3bcb51d35 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -409,4 +409,16 @@ interface ITelecomService { */ void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId, String callingPackage); + + /** + * @see TelecomServiceImpl#hasForegroundServiceDelegation + */ + boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle, + String callingPackage); + void setMetricsTestMode(boolean enabled); + + /** + * @see TelecomServiceImpl#waitForAudioToUpdate + */ + void waitForAudioToUpdate(boolean expectActive); } |