Snap for 12397640 from 6b1ba80992f25470a492c661315d98a5c270d045 to 24Q4-release

Change-Id: I2dc0263ae62a05a27f274e458dfddb4b2147ed44
diff --git a/service/java/com/android/os/profiling/ProfilingService.java b/service/java/com/android/os/profiling/ProfilingService.java
index b21181f..565b442 100644
--- a/service/java/com/android/os/profiling/ProfilingService.java
+++ b/service/java/com/android/os/profiling/ProfilingService.java
@@ -125,7 +125,7 @@
     // Request UUID key indexed storage of active tracing sessions. Currently only 1 active session
     // is supported at a time, but this will be used in future to support multiple.
     @VisibleForTesting
-    public ArrayMap<String, TracingSession> mTracingSessions = new ArrayMap<>();
+    public ArrayMap<String, TracingSession> mActiveTracingSessions = new ArrayMap<>();
 
     // uid indexed storage of completed tracing sessions that have not yet successfully handled the
     // result.
@@ -136,6 +136,51 @@
     @GuardedBy("mLock")
     private boolean mKeepUnredactedTrace = false;
 
+    /**
+     * State the {@link TracingSession} is in.
+     *
+     * State represents the most recently confirmed completed step in the process. Steps represent
+     * save points which the process would have to go back to if it did not successfully reach the
+     * next step.
+     *
+     * States are sequential. It can be expected that state value will only increase throughout a
+     * sessions life.
+     *
+     * At different states, the containing object can be assumed to exist in different data
+     * structures as follows:
+     * REQUESTED - Local only, not in any data structure.
+     * APPROVED - Local only, not in any data structure.
+     * PROFILING_STARTED - Stored in {@link mActiveTracingSessions}.
+     * PROFILING_FINISHED - Stored in {@link mQueuedTracingResults}.
+     * REDACTED - Stored in {@link mQueuedTracingResults}.
+     * COPIED_FILE - Stored in {@link mQueuedTracingResults}.
+     * ERROR_OCCURRED - Stored in {@link mQueuedTracingResults}.
+     * NOTIFIED_REQUESTER - Stored in {@link mQueuedTracingResults}.
+     * CLEANED_UP - Local only, not in any data structure.
+     */
+    public enum TracingState {
+        // Intentionally skipping 0 since proto, which willl be used for persist, treats it as
+        // unset.
+        REQUESTED(1),
+        APPROVED(2),
+        PROFILING_STARTED(3),
+        PROFILING_FINISHED(4),
+        REDACTED(5),
+        COPIED_FILE(6),
+        ERROR_OCCURRED(7),
+        NOTIFIED_REQUESTER(8),
+        CLEANED_UP(9);
+
+        private final int mValue;
+        TracingState(int value) {
+            mValue = value;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+    }
+
     @VisibleForTesting
     public ProfilingService(Context context) {
         mContext = context;
@@ -241,6 +286,103 @@
         }, mClearTemporaryDirectoryBootDelayMs);
     }
 
+    /**
+     * This is the core method that keeps the profiling flow moving.
+     *
+     * This is the only way that state should be set. Do not use {@link TracingSession#setState}
+     * directly.
+     *
+     * The passed newState represents the state that was just completed. Passing null for new state
+     * will continue using the current state as the last completed state, this is intended only for
+     * resuming the queue.
+     *
+     * Generally, this should be the last call in a method before returning.
+     */
+    @VisibleForTesting
+    public void advanceTracingSession(TracingSession session, @Nullable TracingState newState) {
+        if (newState == null) {
+            if (session.getRetryCount() == 0) {
+                // The new state should only be null if this is triggered from the queue in which
+                // case the retry count should be greater than 0. If retry count is 0 here then
+                // we're in an unexpected state. Cleanup and discard. Result will be lost.
+                cleanupTracingSession(session);
+                return;
+            }
+        } else if (newState == session.getState()) {
+            // This should never happen.
+            // If the state is not actually changing then we may find ourselves in an infinite
+            // loop. Terminate this attempt and increment the retry count to ensure there's a
+            // path to breaking out of a potential infinite queue retries.
+            session.incrementRetryCount();
+            return;
+        } else if (newState.getValue() < session.getState().getValue()) {
+            // This should also never happen.
+            // States should always move forward. If the state is trying to move backwards then
+            // we don't actually know what to do next. Clean up the session and delete
+            // everything. Results will be lost.
+            cleanupTracingSession(session);
+            return;
+        } else {
+            // The new state is not null so update the sessions state.
+            session.setState(newState);
+        }
+
+        switch (session.getState()) {
+            case REQUESTED:
+                // This should never happen as requested state is expected to handled by the request
+                // method, the first actionable state is approved. Ignore it.
+                if (DEBUG) {
+                    Log.e(TAG, "Session attempting to advance with REQUESTED state unsupported.");
+                }
+                break;
+            case APPROVED:
+                // Session has been approved by rate limiter, so continue on to start profiling.
+                startProfiling(session);
+                break;
+            case PROFILING_STARTED:
+                // Profiling has been successfully started. Next step depends on whether or not the
+                // profiling is alive.
+                if (session.getActiveTrace() == null || !session.getActiveTrace().isAlive()
+                        || session.getProcessResultRunnable() == null) {
+                    // This really should not happen, but if profiling is not in correct started
+                    // state then try to stop and continue processing it.
+                    stopProfiling(session);
+                } // else: do nothing. The runnable we just verified exists will return us to this
+                // method when profiling is finished.
+                break;
+            case PROFILING_FINISHED:
+                // Next step depends on whether or not the result requires redaction.
+                if (needsRedaction(session)) {
+                    // Redaction needed, kick it off.
+                    handleRedactionRequiredResult(session);
+                } else {
+                    // No redaction needed, move straight to copying to app storage.
+                    beginMoveFileToAppStorage(session);
+                }
+                break;
+            case REDACTED:
+                // Redaction completed, move on to copying to app storage.
+                beginMoveFileToAppStorage(session);
+                break;
+            case COPIED_FILE:
+                // File has already been copied to app storage, proceed to callback.
+                session.setError(ProfilingResult.ERROR_NONE);
+                processTracingSessionResultCallback(session, true /* Continue advancing session */);
+                break;
+            case ERROR_OCCURRED:
+                // An error has occurred, proceed to callback.
+                processTracingSessionResultCallback(session, true /* Continue advancing session */);
+                break;
+            case NOTIFIED_REQUESTER:
+                // Callback has been completed successfully, start cleanup.
+                cleanupTracingSession(session);
+                break;
+            case CLEANED_UP:
+                // Session was cleaned up, nothing left to do.
+                break;
+        }
+    }
+
     /** Perform a temporary directory cleanup if it has been long enough to warrant one. */
     private void maybeCleanupTemporaryDirectory() {
         synchronized (mLock) {
@@ -318,9 +460,9 @@
         List<String> filenames = new ArrayList<String>();
 
         // If active sessions is not empty, iterate through and add the filenames from each.
-        if (!mTracingSessions.isEmpty()) {
-            for (int i = 0; i < mTracingSessions.size(); i++) {
-                TracingSession session = mTracingSessions.valueAt(i);
+        if (!mActiveTracingSessions.isEmpty()) {
+            for (int i = 0; i < mActiveTracingSessions.size(); i++) {
+                TracingSession session = mActiveTracingSessions.valueAt(i);
                 String filename = session.getFileName();
                 if (filename != null) {
                     filenames.add(filename);
@@ -432,7 +574,8 @@
             try {
                 TracingSession session = new TracingSession(profilingType, params, filePath, uid,
                         packageName, tag, keyMostSigBits, keyLeastSigBits);
-                startProfiling(session);
+                advanceTracingSession(session, TracingState.APPROVED);
+                return;
             } catch (IllegalArgumentException e) {
                 // This should not happen, it should have been caught when checking rate limiter.
                 // Issue with the request. Apps fault.
@@ -609,43 +752,39 @@
             return;
         }
 
-        // Only copy the file if we haven't previously.
-        if (session.getState().getValue() < TracingSession.TracingState.COPIED_FILE.getValue()) {
-            // Setup file streams.
-            try {
-                tempPerfettoFileInStream = new FileInputStream(tempResultFile);
-            } catch (IOException e) {
-                // IO Exception opening temp perfetto file. No result.
-                if (DEBUG) Log.d(TAG, "Exception opening temp perfetto file.", e);
-                finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
-                        appFileOutStream, false);
-                return;
-            }
+        // Setup file streams.
+        try {
+            tempPerfettoFileInStream = new FileInputStream(tempResultFile);
+        } catch (IOException e) {
+            // IO Exception opening temp perfetto file. No result.
+            if (DEBUG) Log.d(TAG, "Exception opening temp perfetto file.", e);
+            finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
+                    appFileOutStream, false);
+            return;
+        }
 
-            // Obtain a file descriptor for the result file in app storage from
-            // {@link ProfilingManager}
-            if (fileDescriptor != null) {
-                appFileOutStream = new FileOutputStream(fileDescriptor.getFileDescriptor());
-            }
+        // Obtain a file descriptor for the result file in app storage from
+        // {@link ProfilingManager}
+        if (fileDescriptor != null) {
+            appFileOutStream = new FileOutputStream(fileDescriptor.getFileDescriptor());
+        }
 
-            if (appFileOutStream == null) {
-                finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
-                        appFileOutStream, false);
-                return;
-            }
+        if (appFileOutStream == null) {
+            finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
+                    appFileOutStream, false);
+            return;
+        }
 
-            // Now copy the file over.
-            try {
-                FileUtils.copy(tempPerfettoFileInStream, appFileOutStream);
-                session.setState(TracingSession.TracingState.COPIED_FILE);
-            } catch (IOException e) {
-                // Exception writing to local app file. Attempt to delete the bad copy.
-                deleteBadCopiedFile(session);
-                if (DEBUG) Log.d(TAG, "Exception writing to local app file.", e);
-                finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
-                        appFileOutStream, false);
-                return;
-            }
+        // Now copy the file over.
+        try {
+            FileUtils.copy(tempPerfettoFileInStream, appFileOutStream);
+        } catch (IOException e) {
+            // Exception writing to local app file. Attempt to delete the bad copy.
+            deleteBadCopiedFile(session);
+            if (DEBUG) Log.d(TAG, "Exception writing to local app file.", e);
+            finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
+                    appFileOutStream, false);
+            return;
         }
 
         finishReceiveFileDescriptor(session, fileDescriptor, tempPerfettoFileInStream,
@@ -679,15 +818,21 @@
         }
 
         if (session != null) {
-            finishProcessingResult(session, succeeded);
-        }
-    }
+            if (succeeded) {
+                advanceTracingSession(session, TracingState.COPIED_FILE);
+            } else {
+                // Couldn't move file. File is still in temp directory and will be tried later.
+                // Leave state unchanged so it can get triggered again from the queue, but update
+                // the error and trigger a callback.
+                if (DEBUG) Log.d(TAG, "Couldn't move file to app storage.");
+                session.setError(ProfilingResult.ERROR_FAILED_POST_PROCESSING,
+                        "Failed to copy result to app storage. May try again later.");
+                processTracingSessionResultCallback(session, false /* Do not continue */);
+            }
 
-    private void processResultCallback(TracingSession session, int status, @Nullable String error) {
-        processResultCallback(session.getUid(), session.getKeyMostSigBits(),
-                session.getKeyLeastSigBits(), status,
-                session.getDestinationFileName(OUTPUT_FILE_RELATIVE_PATH),
-                session.getTag(), error);
+            // Clean up temporary directory if it has been long enough to warrant it.
+            maybeCleanupTemporaryDirectory();
+        }
     }
 
     /**
@@ -695,25 +840,68 @@
      * per context that the app created a manager instance with. As we do not know on this service
      * side which callbacks need to be triggered with this result, trigger all of them and let them
      * decide whether to finish delivering it.
+     *
+     * Call this method if a {@link TracingSession} already exists. If no session exists yet, call
+     * {@link #processResultCallback} directly instead.
+     *
+     * @param session           The session for which to callback and potentially advance.
+     * @param continueAdvancing Whether to continue advancing or stop after attempting the callback.
      */
-    private void processResultCallback(int uid, long keyMostSigBits, long keyLeastSigBits,
+    @VisibleForTesting
+    public void processTracingSessionResultCallback(TracingSession session,
+            boolean continueAdvancing) {
+        boolean succeeded = processResultCallback(session.getUid(), session.getKeyMostSigBits(),
+                session.getKeyLeastSigBits(), session.getErrorStatus(),
+                session.getDestinationFileName(OUTPUT_FILE_RELATIVE_PATH),
+                session.getTag(), session.getErrorMessage());
+
+        if (continueAdvancing && succeeded) {
+            advanceTracingSession(session, TracingState.NOTIFIED_REQUESTER);
+        }
+    }
+
+    /**
+     * An app can register multiple callbacks between this service and {@link ProfilingManager}, one
+     * per context that the app created a manager instance with. As we do not know on this service
+     * side which callbacks need to be triggered with this result, trigger all of them and let them
+     * decide whether to finish delivering it.
+     *
+     * Call this directly only if no {@link TracingSession} exists yet. If a session already exists,
+     * call {@link #processTracingSessionResultCallback} instead.
+     *
+     * @return whether at least one callback was successfully sent to the app.
+     */
+    private boolean processResultCallback(int uid, long keyMostSigBits, long keyLeastSigBits,
             int status, @Nullable String filePath, @Nullable String tag, @Nullable String error) {
         List<IProfilingResultCallback> perUidCallbacks = mResultCallbacks.get(uid);
         if (perUidCallbacks == null || perUidCallbacks.isEmpty()) {
             // No callbacks, nowhere to notify with result or failure.
             if (DEBUG) Log.d(TAG, "No callback to ProfilingManager, callback dropped.");
-            return;
+            return false;
         }
 
+        boolean succeeded = false;
         for (int i = 0; i < perUidCallbacks.size(); i++) {
             try {
-                perUidCallbacks.get(i).sendResult(filePath, keyMostSigBits, keyLeastSigBits, status,
-                        tag, error);
+                if (status == ProfilingResult.ERROR_NONE) {
+                    perUidCallbacks.get(i).sendResult(
+                            filePath, keyMostSigBits, keyLeastSigBits, status, tag, error);
+                } else {
+                    perUidCallbacks.get(i).sendResult(
+                            null, keyMostSigBits, keyLeastSigBits, status, tag, error);
+                }
+                // One success is all we need to know that a callback was sent to the app.
+                // This is not perfect but sufficient given we cannot verify the success of
+                // individual listeners without either a blocking binder call into the app or an
+                // extra binder call back from the app.
+                succeeded = true;
             } catch (RemoteException e) {
                 // Failed to send result. Ignore.
                 if (DEBUG) Log.d(TAG, "Exception processing result callback", e);
             }
         }
+
+        return succeeded;
     }
 
     private void startProfiling(final TracingSession session)
@@ -739,8 +927,9 @@
         } catch (IllegalArgumentException e) {
             // Request couldn't be processed. This shouldn't happen.
             if (DEBUG) Log.d(TAG, "Request couldn't be processed", e);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_INVALID_REQUEST,
-                    e.getMessage());
+            session.setError(ProfilingResult.ERROR_FAILED_INVALID_REQUEST, e.getMessage());
+            moveSessionToQueue(session);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
 
         }
@@ -766,16 +955,16 @@
             // If we made it this far the trace is running, save the session.
             session.setActiveTrace(activeTrace);
             session.setProfilingStartTimeMs(System.currentTimeMillis());
-            mTracingSessions.put(session.getKey(), session);
+            mActiveTracingSessions.put(session.getKey(), session);
         } catch (Exception e) {
             // Catch all exceptions related to starting process as they'll all be handled similarly.
             if (DEBUG) Log.d(TAG, "Trace couldn't be started", e);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_EXECUTING, null);
+            session.setError(ProfilingResult.ERROR_FAILED_EXECUTING, "Trace couldn't be started");
+            moveSessionToQueue(session);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
         }
 
-        session.setState(TracingSession.TracingState.PROFILING_STARTED);
-
         // Create post process runnable, store it, and schedule it.
         session.setProcessResultRunnable(new Runnable() {
             @Override
@@ -785,6 +974,8 @@
             }
         });
         getHandler().postDelayed(session.getProcessResultRunnable(), postProcessingInitialDelayMs);
+
+        advanceTracingSession(session, TracingState.PROFILING_STARTED);
     }
 
     /**
@@ -795,7 +986,6 @@
         complete results will be processed and returned to the client.
      */
     private void checkProfilingCompleteRescheduleIfNeeded(TracingSession session) {
-
         long processingTimeRemaining = session.getMaxProfilingTimeAllowedMs()
                 - (System.currentTimeMillis() - session.getProfilingStartTimeMs());
 
@@ -812,21 +1002,22 @@
         } else {
             // complete, process results and deliver.
             session.setProcessResultRunnable(null);
-            processResult(session);
+            moveSessionToQueue(session);
+            advanceTracingSession(session, TracingState.PROFILING_FINISHED);
         }
     }
 
     /** Stop any active profiling sessions belonging to the provided uid. */
     private void stopAllProfilingForUid(int uid) {
-        if (mTracingSessions.isEmpty()) {
+        if (mActiveTracingSessions.isEmpty()) {
             // If there are no active traces, then there are none for this uid.
             return;
         }
 
         // Iterate through active sessions and stop profiling if they belong to the provided uid.
         // Note: Currently, this will only ever have 1 session.
-        for (int i = 0; i < mTracingSessions.size(); i++) {
-            TracingSession session = mTracingSessions.valueAt(i);
+        for (int i = 0; i < mActiveTracingSessions.size(); i++) {
+            TracingSession session = mActiveTracingSessions.valueAt(i);
             if (session.getUid() == uid) {
                 stopProfiling(session);
             }
@@ -834,7 +1025,7 @@
     }
 
     private void stopProfiling(String key) throws RuntimeException {
-        TracingSession session = mTracingSessions.get(key);
+        TracingSession session = mActiveTracingSessions.get(key);
         stopProfiling(session);
     }
 
@@ -873,8 +1064,8 @@
     }
 
     public boolean areAnyTracesRunning() throws RuntimeException {
-        for (int i = 0; i < mTracingSessions.size(); i++) {
-            if (isTraceRunning(mTracingSessions.keyAt(i))) {
+        for (int i = 0; i < mActiveTracingSessions.size(); i++) {
+            if (isTraceRunning(mActiveTracingSessions.keyAt(i))) {
                 return true;
             }
         }
@@ -883,9 +1074,9 @@
 
     /**
      * Cleanup the data structure of active sessions. Non active sessions are never expected to be
-     * present in {@link mTracingSessions} as they would be moved to {@link mQueuedTracingResults}
-     * when profiling completes. If a session is present but not running, remove it. If a session
-     * has a not alive process, try to stop it.
+     * present in {@link mActiveTracingSessions} as they would be moved to
+     * {@link mQueuedTracingResults} when profiling completes. If a session is present but not
+     * running, remove it. If a session has a not alive process, try to stop it.
      */
     public void cleanupActiveTracingSessions() throws RuntimeException {
         // Create a temporary list to store the keys of sessions to be stopped.
@@ -893,13 +1084,13 @@
 
         // Iterate through in reverse order so we can immediately remove the non running sessions
         // that don't have to be stopped.
-        for (int i = mTracingSessions.size() - 1; i >= 0; i--) {
-            String key = mTracingSessions.keyAt(i);
-            TracingSession session = mTracingSessions.get(key);
+        for (int i = mActiveTracingSessions.size() - 1; i >= 0; i--) {
+            String key = mActiveTracingSessions.keyAt(i);
+            TracingSession session = mActiveTracingSessions.get(key);
 
             if (session == null || session.getActiveTrace() == null) {
                 // Profiling isn't running, remove from list.
-                mTracingSessions.removeAt(i);
+                mActiveTracingSessions.removeAt(i);
             } else if (!session.getActiveTrace().isAlive()) {
                 // Profiling process exists but isn't alive, add to list of sessions to stop. Do not
                 // stop here due to potential unanticipated modification of list being iterated
@@ -917,7 +1108,7 @@
     }
 
     public boolean isTraceRunning(String key) throws RuntimeException {
-        TracingSession session = mTracingSessions.get(key);
+        TracingSession session = mActiveTracingSessions.get(key);
         if (session == null || session.getActiveTrace() == null) {
             // No subprocess, nothing running.
             if (DEBUG) Log.d(TAG, "No subprocess, nothing running.");
@@ -940,14 +1131,14 @@
      */
     @VisibleForTesting
     public void beginMoveFileToAppStorage(TracingSession session) {
-        if (session.getState() == TracingSession.TracingState.DISCARDED) {
-            // This should not have happened, if the session was discarded why are we trying to
-            // continue processing it? Remove from all data stores just in case.
+        if (session.getState().getValue() >= TracingState.ERROR_OCCURRED.getValue()) {
+            // This should not have happened, if the session has a state of error or later then why
+            // are we trying to continue processing it? Remove from all data stores just in case.
             if (DEBUG) {
-                Log.d(TAG, "Attempted beginMoveFileToAppStorage on a session with status discarded"
+                Log.d(TAG, "Attempted beginMoveFileToAppStorage on a session with status error"
                         + " or an invalid status.");
             }
-            mTracingSessions.remove(session.getKey());
+            mActiveTracingSessions.remove(session.getKey());
             cleanupTracingSession(session);
             return;
         }
@@ -964,25 +1155,6 @@
     }
 
     /**
-     * Finish processing profiling result by sending the appropriate callback and cleaning up
-     * temporary directory.
-     */
-    @VisibleForTesting
-    public void finishProcessingResult(TracingSession session, boolean success) {
-        if (success) {
-            processResultCallback(session, ProfilingResult.ERROR_NONE, null);
-            cleanupTracingSession(session);
-        } else {
-            // Couldn't move file. File is still in temp directory and will be tried later.
-            if (DEBUG) Log.d(TAG, "Couldn't move file to app storage.");
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_POST_PROCESSING, null);
-        }
-
-        // Clean up temporary directory if it has been long enough to warrant it.
-        maybeCleanupTemporaryDirectory();
-    }
-
-    /**
      * Delete a file which failed to copy via ProfilingManager.
      */
     private void deleteBadCopiedFile(TracingSession session) {
@@ -1039,32 +1211,9 @@
         if (DEBUG) Log.d(TAG, "Failed to obtain file descriptor from callbacks.");
     }
 
-
-    // processResult will be called after every profiling type is collected, traces will go
-    // through a redaction process before being returned to the client.  All other profiling types
-    // can be returned as is.
-    private void processResult(TracingSession session) {
-        // Move this session from active to queued results.
-        List<TracingSession> queuedResults = mQueuedTracingResults.get(session.getUid());
-        if (queuedResults == null) {
-            queuedResults = new ArrayList<TracingSession>();
-            mQueuedTracingResults.put(session.getUid(), queuedResults);
-        }
-        queuedResults.add(session);
-        mTracingSessions.remove(session.getKey());
-
-        session.setState(TracingSession.TracingState.PROFILING_FINISHED);
-
-        if (session.getProfilingType() == ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE) {
-            handleTraceResult(session);
-        } else {
-            beginMoveFileToAppStorage(session);
-        }
-    }
-
-    /** Handle a trace result by attempting to kick off redaction process. */
+    /** Handle a result which required redaction by attempting to kick off redaction process. */
     @VisibleForTesting
-    public void handleTraceResult(TracingSession session) {
+    public void handleRedactionRequiredResult(TracingSession session) {
         try {
             // We need to create an empty file for the redaction process to write the output into.
             File emptyRedactedTraceFile = new File(TEMP_TRACE_PATH
@@ -1072,7 +1221,8 @@
             emptyRedactedTraceFile.createNewFile();
         } catch (Exception exception) {
             if (DEBUG) Log.e(TAG, "Creating empty redacted file failed.", exception);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_POST_PROCESSING, null);
+            session.setError(ProfilingResult.ERROR_FAILED_POST_PROCESSING);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
         }
 
@@ -1088,9 +1238,11 @@
             session.setRedactionStartTimeMs(System.currentTimeMillis());
         } catch (Exception exception) {
             if (DEBUG) Log.e(TAG, "Redaction failed to run completely.", exception);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_POST_PROCESSING, null);
+            session.setError(ProfilingResult.ERROR_FAILED_POST_PROCESSING);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
         }
+
         session.setProcessResultRunnable(new Runnable() {
 
             @Override
@@ -1098,7 +1250,6 @@
                 checkRedactionStatus(session);
             }
         });
-
         getHandler().postDelayed(session.getProcessResultRunnable(),
                 mRedactionCheckFrequencyMs);
     }
@@ -1118,11 +1269,12 @@
 
             session.getActiveRedaction().destroyForcibly();
             session.setProcessResultRunnable(null);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_POST_PROCESSING,
-                    null);
-
+            session.setError(ProfilingResult.ERROR_FAILED_POST_PROCESSING);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
         }
+
+        // Schedule the next check.
         getHandler().postDelayed(session.getProcessResultRunnable(),
                 Math.min(mRedactionCheckFrequencyMs, mRedactionMaxRuntimeAllottedMs
                         - (System.currentTimeMillis() - session.getRedactionStartTimeMs())));
@@ -1138,7 +1290,8 @@
                         redactionErrorCode));
             }
             cleanupTracingSession(session);
-            processResultCallback(session, ProfilingResult.ERROR_FAILED_POST_PROCESSING, null);
+            session.setError(ProfilingResult.ERROR_FAILED_POST_PROCESSING);
+            advanceTracingSession(session, TracingState.ERROR_OCCURRED);
             return;
         }
 
@@ -1154,9 +1307,7 @@
             }
         }
 
-        session.setState(TracingSession.TracingState.REDACTED);
-
-        beginMoveFileToAppStorage(session);
+        advanceTracingSession(session, TracingState.REDACTED);
     }
 
     /**
@@ -1186,34 +1337,8 @@
             }
             session.incrementRetryCount();
 
-            switch (session.getState()) {
-                case NOT_STARTED:
-                case PROFILING_STARTED:
-                    // This should never happen as the session should not be in queuedSessions until
-                    // past this state, but run stop and cleanup just in case.
-                    stopProfiling(session);
-                    cleanupTracingSession(session);
-                    break;
-                case PROFILING_FINISHED:
-                    if (session.getProfilingType()
-                            == ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE) {
-                        handleTraceResult(session);
-                    } else {
-                        beginMoveFileToAppStorage(session);
-                    }
-                    break;
-                case REDACTED:
-                    beginMoveFileToAppStorage(session);
-                    break;
-                case COPIED_FILE:
-                    finishProcessingResult(session, true);
-                    break;
-                case DISCARDED:
-                    // This should never happen as this state should only occur after cleanup of
-                    // this file.
-                    cleanupTracingSession(session, queuedSessions);
-                    break;
-            }
+            // Advance with new state null so that it picks up where it left off.
+            advanceTracingSession(session, null);
         }
 
         // Now attempt to cleanup the queue.
@@ -1252,7 +1377,8 @@
      *
      * Cleanup will attempt to delete the temporary file(s) and then remove it from the queue.
      */
-    private void cleanupTracingSession(TracingSession session) {
+    @VisibleForTesting
+    public void cleanupTracingSession(TracingSession session) {
         List<TracingSession> queuedSessions = mQueuedTracingResults.get(session.getUid());
         cleanupTracingSession(session, queuedSessions);
     }
@@ -1264,7 +1390,7 @@
      * Cleanup will attempt to delete the temporary file(s) and then remove it from the queue.
      */
     private void cleanupTracingSession(TracingSession session,
-            List<TracingSession> queuedSessions) {
+            @Nullable List<TracingSession> queuedSessions) {
         // Delete all files
         if (session.getProfilingType() == ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE) {
             // If type is trace, try to delete the temp file only if {@link mKeepUnredactedTrace} is
@@ -1285,12 +1411,14 @@
 
         }
 
-        session.setState(TracingSession.TracingState.DISCARDED);
-        queuedSessions.remove(session);
-
-        if (queuedSessions.isEmpty()) {
-            mQueuedTracingResults.remove(session.getUid());
+        if (queuedSessions != null) {
+            queuedSessions.remove(session);
+            if (queuedSessions.isEmpty()) {
+                mQueuedTracingResults.remove(session.getUid());
+            }
         }
+
+        advanceTracingSession(session, TracingState.CLEANED_UP);
     }
 
     /**
@@ -1311,6 +1439,27 @@
         }
     }
 
+    /**
+     * Move session to list of queued sessions. Removes the session from the list of active
+     * sessions, if it is present.
+     *
+     * Sessions are expected to be in the queue when their states are between PROFILING_FINISHED and
+     * NOTIFIED_REQUESTER, inclusive.
+     */
+    private void moveSessionToQueue(TracingSession session) {
+        List<TracingSession> queuedResults = mQueuedTracingResults.get(session.getUid());
+        if (queuedResults == null) {
+            queuedResults = new ArrayList<TracingSession>();
+            mQueuedTracingResults.put(session.getUid(), queuedResults);
+        }
+        queuedResults.add(session);
+        mActiveTracingSessions.remove(session.getKey());
+    }
+
+    private boolean needsRedaction(TracingSession session) {
+        return session.getProfilingType() == ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE;
+    }
+
     private Handler getHandler() {
         if (mHandler == null) {
             mHandler = new Handler(mHandlerThread.getLooper());
diff --git a/service/java/com/android/os/profiling/TracingSession.java b/service/java/com/android/os/profiling/TracingSession.java
index a0f1562..b2a8c2d 100644
--- a/service/java/com/android/os/profiling/TracingSession.java
+++ b/service/java/com/android/os/profiling/TracingSession.java
@@ -16,6 +16,8 @@
 
 package android.os.profiling;
 
+import static android.os.profiling.ProfilingService.TracingState;
+
 import android.os.Bundle;
 
 import java.util.UUID;
@@ -25,25 +27,6 @@
  */
 public final class TracingSession {
 
-    public enum TracingState {
-        NOT_STARTED(0),
-        PROFILING_STARTED(1),
-        PROFILING_FINISHED(2),
-        REDACTED(3),
-        COPIED_FILE(4),
-        DISCARDED(5);
-
-        private final int mValue;
-
-        TracingState(int value) {
-            mValue = value;
-        }
-
-        public int getValue() {
-            return mValue;
-        }
-    }
-
     private Process mActiveTrace;
     private Process mActiveRedaction;
     private Runnable mProcessResultRunnable;
@@ -64,6 +47,10 @@
     private int mRetryCount = 0;
     private long mProfilingStartTimeMs;
     private int mMaxProfilingTimeAllowedMs = 0;
+    private String mErrorMessage = null;
+
+    // Expected to be populated with ProfilingResult.ERROR_* values.
+    private int mErrorStatus = -1; // Default to invalid value.
 
     public TracingSession(int profilingType, Bundle params, String appFilePath, int uid,
                 String packageName, String tag, long keyMostSigBits, long keyLeastSigBits) {
@@ -75,7 +62,7 @@
         mTag = tag;
         mKeyMostSigBits = keyMostSigBits;
         mKeyLeastSigBits = keyLeastSigBits;
-        mState = TracingState.NOT_STARTED;
+        mState = TracingState.REQUESTED;
     }
 
     public byte[] getConfigBytes() throws IllegalArgumentException {
@@ -136,6 +123,10 @@
         mRetryCount = retryCount;
     }
 
+    /**
+     * Do not call directly!
+     * State should only be updated with {@link ProfilingService#advanceStateAndContinue}.
+     */
     public void setState(TracingState state) {
         mState = state;
     }
@@ -149,6 +140,20 @@
         mProfilingStartTimeMs = startTime;
     }
 
+    /**
+     * Update error status. Also overrides error message to null as the two fields must be set
+     * together to ensure they make sense.
+     */
+    public void setError(int status) {
+        setError(status, null);
+    }
+
+    /** Update error status and message. */
+    public void setError(int status, String message) {
+        mErrorStatus = status;
+        mErrorMessage = message;
+    }
+
     public Process getActiveTrace() {
         return mActiveTrace;
     }
@@ -230,4 +235,12 @@
     public int getRetryCount() {
         return mRetryCount;
     }
+
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    public int getErrorStatus() {
+        return mErrorStatus;
+    }
 }
diff --git a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
index b164310..fc11e73 100644
--- a/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
+++ b/tests/cts/src/android/profiling/cts/ProfilingServiceTests.java
@@ -16,6 +16,8 @@
 
 package android.profiling.cts;
 
+import static android.os.profiling.ProfilingService.TracingState;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -23,9 +25,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -325,7 +328,7 @@
     @Test
     public void testAreAnyTracesRunning_True() {
         // Ensure no active tracing sessions tracked.
-        mProfilingService.mTracingSessions.clear();
+        mProfilingService.mActiveTracingSessions.clear();
         assertFalse(mProfilingService.areAnyTracesRunning());
 
         // Create a tracing session.
@@ -338,7 +341,7 @@
 
         // Add trace to session and session to ProfilingService tracked sessions.
         tracingSession.setActiveTrace(mActiveTrace);
-        mProfilingService.mTracingSessions.put(
+        mProfilingService.mActiveTracingSessions.put(
                 (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
 
         // Confirm check returns that a trace is running.
@@ -348,14 +351,14 @@
     /** Test that checking if any traces are running works when trace is not running. */
     @Test
     public void testAreAnyTracesRunning_False() {
-        mProfilingService.mTracingSessions.clear();
-        assertEquals(0, mProfilingService.mTracingSessions.size());
+        mProfilingService.mActiveTracingSessions.clear();
+        assertEquals(0, mProfilingService.mActiveTracingSessions.size());
         assertFalse(mProfilingService.areAnyTracesRunning());
 
         TracingSession tracingSession = new TracingSession(
                 ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP, null, APP_FILE_PATH, 123,
                 APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
-        mProfilingService.mTracingSessions.put(
+        mProfilingService.mActiveTracingSessions.put(
                 (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
 
         // Confirm no traces are running because the 1 we added is not in a running state.
@@ -365,18 +368,18 @@
     /** Test that cleaning up active traces list works correctly. */
     @Test
     public void testActiveTracesCleanup() {
-        mProfilingService.mTracingSessions.clear();
-        assertEquals(0, mProfilingService.mTracingSessions.size());
+        mProfilingService.mActiveTracingSessions.clear();
+        assertEquals(0, mProfilingService.mActiveTracingSessions.size());
         assertFalse(mProfilingService.areAnyTracesRunning());
 
         TracingSession tracingSession = new TracingSession(
                 ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP, null, APP_FILE_PATH, 123,
                 APP_PACKAGE_NAME, REQUEST_TAG, KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS);
-        mProfilingService.mTracingSessions.put(
+        mProfilingService.mActiveTracingSessions.put(
                 (new UUID(KEY_MOST_SIG_BITS, KEY_LEAST_SIG_BITS)).toString(), tracingSession);
 
         // Confirm the session was added.
-        assertEquals(1, mProfilingService.mTracingSessions.size());
+        assertEquals(1, mProfilingService.mActiveTracingSessions.size());
 
         // Confirm no traces are running because the 1 we added is not in a running state.
         assertFalse(mProfilingService.areAnyTracesRunning());
@@ -385,14 +388,14 @@
         mProfilingService.cleanupActiveTracingSessions();
 
         // Confirm the non running session was cleaned up.
-        assertEquals(0, mProfilingService.mTracingSessions.size());
+        assertEquals(0, mProfilingService.mActiveTracingSessions.size());
     }
 
     /** Test that request cancel trace does nothing if no trace is running. */
     @Test
     public void testRequestCancel_NotRunning() {
         // Ensure no active tracing sessions tracked.
-        mProfilingService.mTracingSessions.clear();
+        mProfilingService.mActiveTracingSessions.clear();
         assertFalse(mProfilingService.areAnyTracesRunning());
 
         // Register callback.
@@ -633,6 +636,89 @@
 
     // TODO: b/333579817 - Add more rate limiter tests
 
+    /** Test that advancing state in forward direction works as expected. */
+    @Test
+    public void testSessionState_AdvanceForwardSucceeds() {
+        // Create a session with some state.
+        TracingSession session = new TracingSession(
+                ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
+                new Bundle(),
+                mContext.getFilesDir().getPath(),
+                FAKE_UID,
+                APP_PACKAGE_NAME,
+                REQUEST_TAG,
+                KEY_LEAST_SIG_BITS,
+                KEY_MOST_SIG_BITS);
+        session.setState(TracingState.PROFILING_FINISHED);
+
+        // Trigger an advance to a subsequent state.
+        mProfilingService.advanceTracingSession(session, TracingState.ERROR_OCCURRED);
+
+        // Ensure it does try to advance.
+        verify(mProfilingService, times(1)).processTracingSessionResultCallback(any(), eq(true));
+    }
+
+    /** Test that advancing state in backwards direction does not work. */
+    @Test
+    public void testSessionState_AdvanceBackwardsFails() {
+        // Override cleanupTracingSession to do nothing or it will call through to
+        // advanceTracingSession after cleanup and we need to confirm that advanceTracingSession is
+        // not immediately called.
+        doNothing().when(mProfilingService).cleanupTracingSession(any());
+
+        // Create a session with some state.
+        TracingSession session = new TracingSession(
+                ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
+                new Bundle(),
+                mContext.getFilesDir().getPath(),
+                FAKE_UID,
+                APP_PACKAGE_NAME,
+                REQUEST_TAG,
+                KEY_LEAST_SIG_BITS,
+                KEY_MOST_SIG_BITS);
+        session.setState(TracingState.APPROVED);
+
+        // Attempt to advance to earlier state.
+        mProfilingService.advanceTracingSession(session, TracingState.REQUESTED);
+
+        // Ensure service determines something is broken, does not try to advance, and triggers a
+        // cleanup.
+        verify(mProfilingService, times(1)).cleanupTracingSession(any());
+    }
+
+    /**
+     * Test that advancing state with a null new state and no retries (i.e. not from queue retry)
+     * does not work. */
+    @Test
+    public void testSessionState_AdvanceNullFails() {
+        // Override cleanupTracingSession to do nothing or it will call through to
+        // advanceTracingSession after cleanup and we need to confirm that advanceTracingSession is
+        // not immediately called.
+        doNothing().when(mProfilingService).cleanupTracingSession(any());
+
+        // Create a session with some state.
+        TracingSession session = new TracingSession(
+                ProfilingManager.PROFILING_TYPE_HEAP_PROFILE,
+                new Bundle(),
+                mContext.getFilesDir().getPath(),
+                FAKE_UID,
+                APP_PACKAGE_NAME,
+                REQUEST_TAG,
+                KEY_LEAST_SIG_BITS,
+                KEY_MOST_SIG_BITS);
+        session.setState(TracingState.REQUESTED);
+
+        // Make sure retry count is 0 (default value).
+        assertEquals(0, session.getRetryCount());
+
+        // Attempt to advance with null new state.
+        mProfilingService.advanceTracingSession(session, null);
+
+        // Ensure service determines something is broken, does not try to advance, and triggers a
+        // cleanup.
+        verify(mProfilingService, times(1)).cleanupTracingSession(any());
+    }
+
     /** Test that adding a specific listener does not trigger handling queued results. */
     @Test
     public void testQueuedResult_RequestSpecificListener() {
@@ -687,7 +773,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.PROFILING_STARTED);
+        session.setState(TracingState.PROFILING_STARTED);
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
 
@@ -726,7 +812,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.PROFILING_FINISHED);
+        session.setState(TracingState.PROFILING_FINISHED);
         session.setRetryCount(3);
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
@@ -764,7 +850,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.PROFILING_FINISHED);
+        session.setState(TracingState.PROFILING_FINISHED);
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(uid, queue);
 
@@ -778,7 +864,7 @@
         // Confirm that the correct path was called. Callback will be for failed post processing
         // because we cannot copy from this context.
         verify(mProfilingService, times(1)).beginMoveFileToAppStorage(any());
-        verify(mProfilingService, times(1)).finishProcessingResult(any(), eq(false));
+        verify(mProfilingService, times(1)).processTracingSessionResultCallback(any(), eq(false));
         assertTrue(callback.mFileRequested);
         assertTrue(callback.mResultSent);
         assertEquals(ProfilingResult.ERROR_FAILED_POST_PROCESSING, callback.mStatus);
@@ -803,7 +889,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.PROFILING_FINISHED);
+        session.setState(TracingState.PROFILING_FINISHED);
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
 
@@ -816,7 +902,7 @@
 
         // Confirm that the correct path was called. Callback will be for failed post processing
         // because we cannot copy from this context.
-        verify(mProfilingService, times(1)).handleTraceResult(any());
+        verify(mProfilingService, times(1)).handleRedactionRequiredResult(any());
         assertTrue(callback.mResultSent);
         assertEquals(ProfilingResult.ERROR_FAILED_POST_PROCESSING, callback.mStatus);
     }
@@ -841,7 +927,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.REDACTED);
+        session.setState(TracingState.REDACTED);
         session.setProfilingStartTimeMs(System.currentTimeMillis());
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(uid, queue);
@@ -855,7 +941,7 @@
 
         // Confirm that the correct path was called.
         verify(mProfilingService, times(1)).beginMoveFileToAppStorage(any());
-        verify(mProfilingService, times(1)).finishProcessingResult(any(), eq(false));
+        verify(mProfilingService, times(1)).processTracingSessionResultCallback(any(), eq(false));
         assertTrue(callback.mFileRequested);
         assertTrue(callback.mResultSent);
         assertEquals(ProfilingResult.ERROR_FAILED_POST_PROCESSING, callback.mStatus);
@@ -884,7 +970,8 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.COPIED_FILE);
+        session.setState(TracingState.COPIED_FILE);
+        session.setError(ProfilingResult.ERROR_NONE);
         queue.add(session);
         mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
 
@@ -896,12 +983,87 @@
         mProfilingService.handleQueuedResults(FAKE_UID);
 
         // Confirm that the correct path was called that a success callback was received.
-        verify(mProfilingService, times(1)).finishProcessingResult(any(), eq(true));
-        assertEquals(ProfilingResult.ERROR_NONE, callback.mStatus);
+        verify(mProfilingService, times(1)).processTracingSessionResultCallback(any(), eq(true));
         assertFalse(mProfilingService.mQueuedTracingResults.contains(FAKE_UID));
     }
 
     /**
+     * Test that a queued result for a session with state of error occurred correctly progresses
+     * to next step of triggering callback.
+     */
+    @Test
+    public void testQueuedResult_ErrorOccurred() {
+        // Clear all existing queued results.
+        mProfilingService.mQueuedTracingResults.clear();
+
+        // Add a in progress session to queue with state error occurred
+        List<TracingSession> queue = new ArrayList<TracingSession>();
+        TracingSession session = new TracingSession(
+                ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
+                new Bundle(),
+                mContext.getFilesDir().getPath(),
+                FAKE_UID,
+                APP_PACKAGE_NAME,
+                REQUEST_TAG,
+                KEY_LEAST_SIG_BITS,
+                KEY_MOST_SIG_BITS);
+        session.setState(TracingState.ERROR_OCCURRED);
+        session.setError(ProfilingResult.ERROR_UNKNOWN);
+        queue.add(session);
+        mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
+
+        // Add a callback directly with fake uid
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.mResultCallbacks.put(FAKE_UID, Arrays.asList(callback));
+
+        // Trigger handle queued results
+        mProfilingService.handleQueuedResults(FAKE_UID);
+
+        // Confirm that the correct path was called that an error callback was received.
+        verify(mProfilingService, times(1)).processTracingSessionResultCallback(any(), eq(true));
+        assertTrue(callback.mResultSent);
+        assertEquals(ProfilingResult.ERROR_UNKNOWN, callback.mStatus);
+    }
+
+    /**
+     * Test that a queued result for a session with state of notified requester correctly progresses
+     * to next step of cleaning up and does not trigger any further callbacks.
+     */
+    @Test
+    public void testQueuedResult_NotifiedRequester() {
+        // Clear all existing queued results.
+        mProfilingService.mQueuedTracingResults.clear();
+
+        // Add a in progress session to queue with state notified requester
+        List<TracingSession> queue = new ArrayList<TracingSession>();
+        TracingSession session = new TracingSession(
+                ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE,
+                new Bundle(),
+                mContext.getFilesDir().getPath(),
+                FAKE_UID,
+                APP_PACKAGE_NAME,
+                REQUEST_TAG,
+                KEY_LEAST_SIG_BITS,
+                KEY_MOST_SIG_BITS);
+        session.setState(TracingState.NOTIFIED_REQUESTER);
+        session.setError(ProfilingResult.ERROR_NONE);
+        queue.add(session);
+        mProfilingService.mQueuedTracingResults.put(FAKE_UID, queue);
+
+        // Add a callback directly with fake uid
+        ProfilingResultCallback callback = new ProfilingResultCallback();
+        mProfilingService.mResultCallbacks.put(FAKE_UID, Arrays.asList(callback));
+
+        // Trigger handle queued results
+        mProfilingService.handleQueuedResults(FAKE_UID);
+
+        // Confirm that the correct path was called.
+        verify(mProfilingService, times(1)).cleanupTracingSession(any());
+        assertFalse(mProfilingService.mQueuedTracingResults.contains(FAKE_UID));
+        assertFalse(callback.mResultSent);
+    }
+
+    /**
      * Test that a queued result that was started longer than max queue time ago is successfully
      * cleaned up when the queue is triggered for a different uid.
      */
@@ -921,7 +1083,7 @@
                 REQUEST_TAG,
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
-        session.setState(TracingSession.TracingState.COPIED_FILE);
+        session.setState(TracingState.COPIED_FILE);
         session.setProfilingStartTimeMs(System.currentTimeMillis() - 1000
                 - ProfilingService.QUEUED_RESULT_MAX_RETAINED_DURATION_MS);
         queue.add(session);
@@ -966,8 +1128,8 @@
                 KEY_LEAST_SIG_BITS,
                 KEY_MOST_SIG_BITS);
         session.setFileName(trackedFile.getName());
-        mProfilingService.mTracingSessions.put(session.getKey(), session);
-        assertEquals(1, mProfilingService.mTracingSessions.size());
+        mProfilingService.mActiveTracingSessions.put(session.getKey(), session);
+        assertEquals(1, mProfilingService.mActiveTracingSessions.size());
 
         // Now trigger the cleanup
         mProfilingService.cleanupTemporaryDirectoryLocked(directory.getPath());