Make sure instance launch trace for an id only appears once

A consecutive launch may contain multiple activities with different
windowing modes or target displays. The separated transition should
not end the trace of the initial launch.

e.g. start A---------------------->A drawn
               |-A starts B-->B drawn (on a different display)
There will still be 2 traces "launching: A" and "launching: B".
But there should be only one trace "launchingActivity" which is
ended by "A drawn".

Bug: 231612200
Test: atest ActivityMetricsLaunchObserverTests# \
            testConsecutiveLaunchOnDifferentDisplay

Change-Id: Ied39e000ef6c8c60b56e228cbdc86b6f9dbe2e6c
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index f2bcd1d..d6c0ab6 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -192,11 +192,10 @@
         /** The sequence id for trace. It is used to map the traces before resolving intent. */
         private static int sTraceSeqId;
         /** The trace format is "launchingActivity#$seqId:$state(:$packageName)". */
-        final String mTraceName;
+        String mTraceName;
 
         LaunchingState() {
             if (!Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
-                mTraceName = null;
                 return;
             }
             // Use an id because the launching app is not yet known before resolving intent.
@@ -205,8 +204,14 @@
             Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName, 0);
         }
 
-        void stopTrace(boolean abort) {
+        void stopTrace(boolean abort, TransitionInfo endInfo) {
             if (mTraceName == null) return;
+            if (!abort && endInfo != mAssociatedTransitionInfo) {
+                // Multiple TransitionInfo can be associated with the same LaunchingState (e.g. a
+                // launching activity launches another activity in a different windowing mode or
+                // display). Only the original associated info can emit a "completed" trace.
+                return;
+            }
             Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName, 0);
             final String launchResult;
             if (mAssociatedTransitionInfo == null) {
@@ -218,6 +223,7 @@
             }
             // Put a supplement trace as the description of the async trace with the same id.
             Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName + launchResult);
+            mTraceName = null;
         }
 
         @VisibleForTesting
@@ -321,7 +327,11 @@
             mProcessSwitch = processSwitch;
             mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
             setLatestLaunchedActivity(r);
-            launchingState.mAssociatedTransitionInfo = this;
+            // The launching state can be reused by consecutive launch. Its original association
+            // shouldn't be changed by a separated transition.
+            if (launchingState.mAssociatedTransitionInfo == null) {
+                launchingState.mAssociatedTransitionInfo = this;
+            }
             if (options != null) {
                 final SourceInfo sourceInfo = options.getSourceInfo();
                 if (sourceInfo != null) {
@@ -908,7 +918,7 @@
             return;
         }
         if (DEBUG_METRICS) Slog.i(TAG, "abort launch cause=" + cause);
-        state.stopTrace(true /* abort */);
+        state.stopTrace(true /* abort */, null /* endInfo */);
         launchObserverNotifyIntentFailed(state.mCurrentTransitionStartTimeNs);
     }
 
@@ -924,7 +934,7 @@
             Slog.i(TAG, "done abort=" + abort + " cause=" + cause + " timestamp=" + timestampNs
                     + " info=" + info);
         }
-        info.mLaunchingState.stopTrace(abort);
+        info.mLaunchingState.stopTrace(abort, info);
         stopLaunchTrace(info);
         final Boolean isHibernating =
                 mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 2fea228..5b909a3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -532,6 +532,9 @@
         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
         setLastExpectedStartedId(activityOnNewDisplay);
         transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay);
+
+        assertWithMessage("The launching state must not include the separated launch")
+                .that(mLaunchingState.contains(activityOnNewDisplay)).isFalse();
     }
 
     @Test