diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index dc6917502e278..e4dc8c2bf7973 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -127,6 +127,12 @@ class ActivityMetricsLogger { private static final int WINDOW_STATE_MULTI_WINDOW = 4; private static final int WINDOW_STATE_INVALID = -1; + /** + * If a launching activity isn't visible within this duration when the device is sleeping, e.g. + * keyguard is locked, its transition info will be dropped. + */ + private static final long UNKNOWN_VISIBILITY_CHECK_DELAY_MS = 3000; + /** * The flag for {@link #notifyActivityLaunching} to skip associating a new launch with an active * transition, in the case the launch is standalone (e.g. from recents). @@ -300,8 +306,7 @@ class ActivityMetricsLogger { } /** @return {@code true} if the activity matches a launched activity in this transition. */ - boolean contains(WindowContainer wc) { - final ActivityRecord r = AppTransitionController.getAppFromContainer(wc); + boolean contains(ActivityRecord r) { return r != null && (r == mLastLaunchedActivity || mPendingDrawActivities.contains(r)); } @@ -473,10 +478,10 @@ class ActivityMetricsLogger { /** @return Non-null {@link TransitionInfo} if the activity is found in an active transition. */ @Nullable - private TransitionInfo getActiveTransitionInfo(WindowContainer wc) { + private TransitionInfo getActiveTransitionInfo(ActivityRecord r) { for (int i = mTransitionInfoList.size() - 1; i >= 0; i--) { final TransitionInfo info = mTransitionInfoList.get(i); - if (info.contains(wc)) { + if (info.contains(r)) { return info; } } @@ -587,8 +592,8 @@ class ActivityMetricsLogger { return; } - if (info != null - && info.mLastLaunchedActivity.mDisplayContent == launchedActivity.mDisplayContent) { + final DisplayContent targetDisplay = launchedActivity.mDisplayContent; + if (info != null && info.mLastLaunchedActivity.mDisplayContent == targetDisplay) { // If we are already in an existing transition on the same display, only update the // activity name, but not the other attributes. @@ -616,6 +621,13 @@ class ActivityMetricsLogger { // As abort for no process switch. launchObserverNotifyIntentFailed(); } + if (targetDisplay.isSleeping()) { + // It is unknown whether the activity can be drawn or not, e.g. ut depends on the + // keyguard states and the attributes or flags set by the activity. If the activity + // keeps invisible in the grace period, the tracker will be cancelled so it won't get + // a very long launch time that takes unlocking as the end of launch. + scheduleCheckActivityToBeDrawn(launchedActivity, UNKNOWN_VISIBILITY_CHECK_DELAY_MS); + } } /** @@ -667,12 +679,15 @@ class ActivityMetricsLogger { * ActivityTaskManagerInternal.APP_TRANSITION_* reasons. */ void notifyTransitionStarting(ArrayMap activityToReason) { - if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting"); + if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting " + activityToReason); final long timestampNs = SystemClock.elapsedRealtimeNanos(); for (int index = activityToReason.size() - 1; index >= 0; index--) { - final WindowContainer wc = activityToReason.keyAt(index); - final TransitionInfo info = getActiveTransitionInfo(wc); + final WindowContainer wc = activityToReason.keyAt(index); + final ActivityRecord activity = wc.asActivityRecord(); + final ActivityRecord r = activity != null ? activity + : wc.getTopActivity(false /* includeFinishing */, true /* includeOverlays */); + final TransitionInfo info = getActiveTransitionInfo(r); if (info == null || info.mLoggedTransitionStarting) { // Ignore any subsequent notifyTransitionStarting. continue; @@ -717,26 +732,32 @@ class ActivityMetricsLogger { Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested + " state=" + r.getState() + " finishing=" + r.finishing); } - if (!r.mVisibleRequested || r.finishing) { - info.removePendingDrawActivity(r); - } - if (info.mLastLaunchedActivity != r) { + if (r.isState(Task.ActivityState.RESUMED) && r.mDisplayContent.isSleeping()) { + // The activity may be launching while keyguard is locked. The keyguard may be dismissed + // after the activity finished relayout, so skip the visibility check to avoid aborting + // the tracking of launch event. return; } - // The activity and its task are passed separately because the activity may be removed from - // the task later. - r.mAtmService.mH.sendMessage(PooledLambda.obtainMessage( - ActivityMetricsLogger::checkVisibility, this, r.getTask(), r)); + if (!r.mVisibleRequested || r.finishing) { + info.removePendingDrawActivity(r); + if (info.mLastLaunchedActivity == r) { + // Check if the tracker can be cancelled because the last launched activity may be + // no longer visible. + scheduleCheckActivityToBeDrawn(r, 0 /* delay */); + } + } } - /** @return {@code true} if the given task has an activity will be drawn. */ - private static boolean hasActivityToBeDrawn(Task t) { - return t.forAllActivities(r -> r.mVisibleRequested && !r.isReportedDrawn() && !r.finishing); + private void scheduleCheckActivityToBeDrawn(@NonNull ActivityRecord r, long delay) { + // The activity and its task are passed separately because it is possible that the activity + // is removed from the task later. + r.mAtmService.mH.sendMessageDelayed(PooledLambda.obtainMessage( + ActivityMetricsLogger::checkActivityToBeDrawn, this, r.getTask(), r), delay); } - private void checkVisibility(Task t, ActivityRecord r) { + /** Cancels the tracking of launch if there won't be an activity to be drawn. */ + private void checkActivityToBeDrawn(Task t, ActivityRecord r) { synchronized (mSupervisor.mService.mGlobalLock) { - final TransitionInfo info = getActiveTransitionInfo(r); // If we have an active transition that's waiting on a certain activity that will be @@ -757,13 +778,14 @@ class ActivityMetricsLogger { // 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)) { + if (t.forAllActivities( + a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) { return; } - if (DEBUG_METRICS) Slog.i(TAG, "notifyVisibilityChanged to invisible activity=" + r); + if (DEBUG_METRICS) Slog.i(TAG, "checkActivityToBeDrawn cancels activity=" + r); logAppTransitionCancel(info); - abort(info, "notifyVisibilityChanged to invisible"); + abort(info, "checkActivityToBeDrawn (invisible or drawn already)"); } } 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 7c9ff79df55ed..2148589529f33 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java @@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import android.app.ActivityOptions; @@ -250,6 +251,19 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase { verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity)); } + @Test + public void testOnActivityLaunchWhileSleeping() { + notifyActivityLaunching(mTopActivity.intent); + notifyActivityLaunched(START_SUCCESS, mTopActivity); + doReturn(true).when(mTopActivity.mDisplayContent).isSleeping(); + mTopActivity.setState(Task.ActivityState.RESUMED, "test"); + mTopActivity.setVisibility(false); + waitHandlerIdle(mAtm.mH); + // Not cancel immediately because in one of real cases, the keyguard may be going away or + // occluded later, then the activity can be drawn. + verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity)); + } + @Test public void testOnReportFullyDrawn() { mActivityOptions = ActivityOptions.makeBasic();