AML: Abort launching metrics if no activities will be drawn

Otherwise it may report a large launch time on an invisible
activity that depends on when to trigger next transition.

Bug: 123355661
Test: atest ActivityMetricsLaunchObserverTests# \
            testOnActivityLaunchCancelled_finishedBeforeDrawn

Change-Id: I6f936929a5ed6ade0e6350d9fde8229d6aaad558
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 9d41d97..04c2083 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -403,8 +403,7 @@
 
         if (launchedActivity != null && launchedActivity.mDrawn) {
             // Launched activity is already visible. We cannot measure windows drawn delay.
-            reset(true /* abort */, info, "launched activity already visible",
-                0L /* timestampNs */);
+            abort(info, "launched activity already visible");
             return;
         }
 
@@ -422,8 +421,7 @@
         if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
                 || windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
             // Failed to launch or it was not a process switch, so we don't care about the timing.
-            reset(true /* abort */, info, "failed to launch or not a process switch",
-                0L /* timestampNs */);
+            abort(info, "failed to launch or not a process switch");
             return;
         } else if (otherWindowModesLaunching) {
             // Don't log this windowing mode but continue with the other windowing modes.
@@ -469,8 +467,7 @@
         final WindowingModeTransitionInfoSnapshot infoSnapshot =
                 new WindowingModeTransitionInfoSnapshot(info);
         if (allWindowsDrawn() && mLoggedTransitionStarting) {
-            reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn",
-                timestampNs /* timestampNs */);
+            reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn", timestampNs);
         }
         return infoSnapshot;
     }
@@ -544,10 +541,11 @@
         mHandler.obtainMessage(MSG_CHECK_VISIBILITY, args).sendToTarget();
     }
 
-    private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
+    /** @return {@code true} if the given task has an activity will be drawn. */
+    private static boolean hasActivityToBeDrawn(TaskRecord t) {
         for (int i = t.getChildCount() - 1; i >= 0; --i) {
             final ActivityRecord r = t.getChildAt(i);
-            if (r.visible && !r.finishing) {
+            if (r.visible && !r.mDrawn && !r.finishing) {
                 return true;
             }
         }
@@ -574,19 +572,20 @@
                 return;
             }
 
-            // Check if there is any activity in the task that is visible and not finishing. If the
-            // launched activity finished before it is drawn and if there is another activity in
-            // the task then that activity will be draw on screen.
-            if (hasVisibleNonFinishingActivity(t)) {
+            // If the task of the launched activity contains any activity to be drawn, then the
+            // window drawn event should report later to complete the transition. Otherwise all
+            // activities in this task may be finished, invisible or drawn, so the transition event
+            // should be cancelled.
+            if (hasActivityToBeDrawn(t)) {
                 return;
             }
 
             if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible activity=" + r);
             logAppTransitionCancel(info);
-            mWindowingModeTransitionInfo.remove(r.getWindowingMode());
-            if (mWindowingModeTransitionInfo.size() == 0) {
-                reset(true /* abort */, info, "notifyVisibilityChanged to invisible",
-                    0L /* timestampNs */);
+            // Abort if this is the only one active transition.
+            if (mWindowingModeTransitionInfo.size() == 1
+                    && mWindowingModeTransitionInfo.get(r.getWindowingMode()) != null) {
+                abort(info, "notifyVisibilityChanged to invisible");
             }
         }
     }
@@ -622,19 +621,25 @@
                 && mWindowingModeTransitionInfo.size() > 0;
     }
 
+    /** Aborts tracking of current launch metrics. */
+    private void abort(WindowingModeTransitionInfo info, String cause) {
+        reset(true /* abort */, info, cause, 0L /* timestampNs */);
+    }
+
     private void reset(boolean abort, WindowingModeTransitionInfo info, String cause,
-        long timestampNs) {
+            long timestampNs) {
+        final boolean isAnyTransitionActive = isAnyTransitionActive();
         if (DEBUG_METRICS) {
-            Slog.i(TAG,
-                "reset abort=" + abort + ",cause=" + cause + ",timestamp=" + timestampNs);
+            Slog.i(TAG, "reset abort=" + abort + " cause=" + cause + " timestamp=" + timestampNs
+                    + " active=" + isAnyTransitionActive);
         }
-        if (!abort && isAnyTransitionActive()) {
+        if (!abort && isAnyTransitionActive) {
             logAppTransitionMultiEvents();
         }
         stopLaunchTrace(info);
 
         // Ignore reset-after reset.
-        if (isAnyTransitionActive()) {
+        if (isAnyTransitionActive) {
             // LaunchObserver callbacks.
             if (abort) {
                 launchObserverNotifyActivityLaunchCancelled(info);
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 03367db..7c867b6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -149,9 +149,7 @@
     public void testOnActivityLaunchFinished() {
         onActivityLaunched();
 
-        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-                SystemClock.elapsedRealtimeNanos());
-
+        notifyTransitionStarting();
         notifyWindowsDrawn(mTopActivity);
 
         verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
@@ -159,10 +157,10 @@
     }
 
     @Test
-    public void testOnActivityLaunchCancelled() {
+    public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched();
 
-        mTopActivity.mDrawn = true;
+        mTopActivity.visible = mTopActivity.mDrawn = true;
 
         // Cannot time already-visible activities.
         mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
@@ -172,6 +170,28 @@
     }
 
     @Test
+    public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
+        mTopActivity.visible = mTopActivity.mDrawn = true;
+
+        // Suppress resume when creating the record because we want to notify logger manually.
+        mSupervisor.beginDeferResume();
+        // Create an activity with different process that meets process switch.
+        final ActivityRecord noDrawnActivity = new ActivityBuilder(mService)
+                .setTask(mTopActivity.getTaskRecord())
+                .setProcessName("other")
+                .build();
+        mSupervisor.readyToResume();
+
+        mActivityMetricsLogger.notifyActivityLaunching(noDrawnActivity.intent);
+        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
+
+        noDrawnActivity.destroyIfPossible("test");
+        mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
+
+        verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity));
+    }
+
+    @Test
     public void testOnReportFullyDrawn() {
         onActivityLaunched();
 
@@ -184,16 +204,21 @@
     private void onActivityLaunchedTrampoline() {
         onIntentStarted();
 
-        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
-
-        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
-
-        // A second, distinct, activity launch is coalesced into the the current app launch sequence
         mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
 
+        verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt());
+
+        // A second, distinct, activity launch is coalesced into the current app launch sequence.
+        mActivityMetricsLogger.notifyActivityLaunched(START_SUCCESS, mTopActivity);
+
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
+    private void notifyTransitionStarting() {
+        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
+                SystemClock.elapsedRealtimeNanos());
+    }
+
     private void notifyWindowsDrawn(ActivityRecord r) {
         mActivityMetricsLogger.notifyWindowsDrawn(r.getWindowingMode(),
                 SystemClock.elapsedRealtimeNanos());
@@ -203,15 +228,12 @@
     public void testOnActivityLaunchFinishedTrampoline() {
         onActivityLaunchedTrampoline();
 
-        mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
-                SystemClock.elapsedRealtimeNanos());
-
+        notifyTransitionStarting();
         notifyWindowsDrawn(mTrampolineActivity);
 
         notifyWindowsDrawn(mTopActivity);
 
-        verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTrampolineActivity),
-                anyLong());
+        verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mTopActivity), anyLong());
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
@@ -219,12 +241,12 @@
     public void testOnActivityLaunchCancelledTrampoline() {
         onActivityLaunchedTrampoline();
 
-        mTrampolineActivity.mDrawn = true;
+        mTopActivity.mDrawn = true;
 
         // Cannot time already-visible activities.
-        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTrampolineActivity);
+        mActivityMetricsLogger.notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
 
-        verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
+        verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
         verifyNoMoreInteractions(mLaunchObserver);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 80f0851..78db6c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -118,7 +118,8 @@
         private ComponentName mComponent;
         private String mTargetActivity;
         private TaskRecord mTaskRecord;
-        private int mUid;
+        private String mProcessName = "name";
+        private int mUid = 12345;
         private boolean mCreateTask;
         private ActivityStack mStack;
         private int mActivityFlags;
@@ -175,6 +176,11 @@
             return this;
         }
 
+        ActivityBuilder setProcessName(String name) {
+            mProcessName = name;
+            return this;
+        }
+
         ActivityBuilder setUid(int uid) {
             mUid = uid;
             return this;
@@ -235,6 +241,7 @@
             aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
             aInfo.applicationInfo.packageName = mComponent.getPackageName();
             aInfo.applicationInfo.uid = mUid;
+            aInfo.processName = mProcessName;
             aInfo.packageName = mComponent.getPackageName();
             if (mTargetActivity != null) {
                 aInfo.targetActivity = mTargetActivity;
@@ -268,7 +275,7 @@
             }
 
             final WindowProcessController wpc = new WindowProcessController(mService,
-                    mService.mContext.getApplicationInfo(), "name", 12345,
+                    mService.mContext.getApplicationInfo(), mProcessName, mUid,
                     UserHandle.getUserId(12345), mock(Object.class),
                     mock(WindowProcessListener.class));
             wpc.setThread(mock(IApplicationThread.class));