Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 504e61e8 authored by Tom Natan's avatar Tom Natan Committed by Android (Google) Code Review
Browse files

Merge "Log AppCompatStateChanged atom." into sc-v2-dev

parents 8c500d58 eb3007fe
Loading
Loading
Loading
Loading
+139 −0
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
@@ -89,6 +91,7 @@ import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;

@@ -157,6 +160,9 @@ class ActivityMetricsLogger {
    private final ArrayList<TransitionInfo> mTransitionInfoList = new ArrayList<>();
    /** Map : Last launched activity => {@link TransitionInfo} */
    private final ArrayMap<ActivityRecord, TransitionInfo> mLastTransitionInfo = new ArrayMap<>();
    /** SparseArray : Package UID => {@link PackageCompatStateInfo} */
    private final SparseArray<PackageCompatStateInfo> mPackageUidToCompatStateInfo =
            new SparseArray<>(0);

    private ArtManagerInternal mArtManagerInternal;
    private final StringBuilder mStringBuilder = new StringBuilder();
@@ -452,6 +458,15 @@ class ActivityMetricsLogger {
        }
    }

    /** Information about the App Compat state logging associated with a package UID . */
    private static final class PackageCompatStateInfo {
        /** All activities that have a visible state. */
        final ArrayList<ActivityRecord> mVisibleActivities = new ArrayList<>();
        /** The last logged state. */
        int mLastLoggedState = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
        @Nullable ActivityRecord mLastLoggedActivity;
    }

    ActivityMetricsLogger(ActivityTaskSupervisor supervisor, Looper looper) {
        mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
        mSupervisor = supervisor;
@@ -775,6 +790,21 @@ class ActivityMetricsLogger {
    /** Makes sure that the reference to the removed activity is cleared. */
    void notifyActivityRemoved(@NonNull ActivityRecord r) {
        mLastTransitionInfo.remove(r);

        final int packageUid = r.info.applicationInfo.uid;
        final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
        if (compatStateInfo == null) {
            return;
        }

        compatStateInfo.mVisibleActivities.remove(r);
        if (compatStateInfo.mLastLoggedActivity == r) {
            compatStateInfo.mLastLoggedActivity = null;
        }
        if (compatStateInfo.mVisibleActivities.isEmpty()) {
            // No need to keep the entry if there are no visible activities.
            mPackageUidToCompatStateInfo.remove(packageUid);
        }
    }

    /**
@@ -1271,6 +1301,115 @@ class ActivityMetricsLogger {
                memoryStat.swapInBytes);
    }

    /**
     * Logs the current App Compat state of the given {@link ActivityRecord} with its package
     * UID, if all of the following hold:
     * <ul>
     *   <li>The current state is different than the last logged state for the package UID of the
     *   activity.
     *   <li>If the current state is NOT_VISIBLE, there is a previously logged state for the
     *   package UID and there are no other visible activities with the same package UID.
     *   <li>The last logged activity with the same package UID is either {@code activity} or the
     *   last logged state is NOT_VISIBLE or NOT_LETTERBOXED.
     * </ul>
     *
     * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code
     * activity} wasn't, looks for the first visible activity with the same package UID that has
     * a letterboxed state, or a non-letterboxed state if there isn't one, and logs that state.
     *
     * <p>This method assumes that the caller is wrapping the call with a synchronized block so
     * that there won't be a race condition between two activities with the same package.
     */
    void logAppCompatState(@NonNull ActivityRecord activity) {
        final int packageUid = activity.info.applicationInfo.uid;
        final int state = activity.getAppCompatState();

        if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
            mPackageUidToCompatStateInfo.put(packageUid, new PackageCompatStateInfo());
        }
        final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
        final int lastLoggedState = compatStateInfo.mLastLoggedState;
        final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;

        final boolean isVisible = state != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
        final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;
        if (isVisible && !visibleActivities.contains(activity)) {
            visibleActivities.add(activity);
        } else if (!isVisible) {
            visibleActivities.remove(activity);
            if (visibleActivities.isEmpty()) {
                // No need to keep the entry if there are no visible activities.
                mPackageUidToCompatStateInfo.remove(packageUid);
            }
        }

        if (state == lastLoggedState) {
            // We don’t want to log the same state twice or log DEFAULT_NOT_VISIBLE before any
            // visible state was logged.
            return;
        }

        if (!isVisible && !visibleActivities.isEmpty()) {
            // There is another visible activity for this package UID.
            if (activity == lastLoggedActivity) {
                // Make sure a new visible state is logged if needed.
                findAppCompatStateToLog(compatStateInfo, packageUid);
            }
            return;
        }

        if (activity != lastLoggedActivity
                && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE
                && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) {
            // Another visible activity for this package UID has logged a letterboxed state.
            return;
        }

        logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
    }

    /**
     * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed
     * state, or a non-letterboxed state if there isn't one, and logs that state for the given
     * {@code packageUid}.
     */
    private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) {
        final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities;

        ActivityRecord activityToLog = null;
        int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
        for (int i = 0; i < visibleActivities.size(); i++) {
            ActivityRecord activity = visibleActivities.get(i);
            int state = activity.getAppCompatState();
            if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
                // This shouldn't happen.
                Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: "
                        + packageUid);
                continue;
            }
            if (stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE || (
                    stateToLog == APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED
                            && state != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED)) {
                activityToLog = activity;
                stateToLog = state;
            }
        }
        if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
            logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
        }
    }

    private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
            int packageUid, PackageCompatStateInfo compatStateInfo) {
        compatStateInfo.mLastLoggedState = state;
        compatStateInfo.mLastLoggedActivity = activity;
        FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);

        if (DEBUG_METRICS) {
            Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
        }
    }

    private ArtManagerInternal getArtManagerInternal() {
        if (mArtManagerInternal == null) {
            // Note that this may be null.
+48 −3
Original line number Diff line number Diff line
@@ -127,6 +127,11 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
@@ -4692,6 +4697,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
                ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);
        mTaskSupervisor.getActivityMetricsLogger().notifyVisibilityChanged(this);
        mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
        logAppCompatState();
    }

    @VisibleForTesting
@@ -7438,6 +7444,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            }
            resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
        }

        logAppCompatState();
    }

    /**
@@ -7447,18 +7455,55 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
     * LetterboxUiController#shouldShowLetterboxUi} for more context.
     */
    boolean areBoundsLetterboxed() {
        return getAppCompatState(/* ignoreVisibility= */ true)
                != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
    }

    /**
     * Logs the current App Compat state via {@link ActivityMetricsLogger#logAppCompatState}.
     */
    private void logAppCompatState() {
        mTaskSupervisor.getActivityMetricsLogger().logAppCompatState(this);
    }

    /**
     * Returns the current App Compat state of this activity.
     *
     * <p>The App Compat state indicates whether the activity is visible and letterboxed, and if so
     * what is the reason for letterboxing. The state is used for logging the time spent in
     * letterbox (sliced by the reason) vs non-letterbox per app.
     */
    int getAppCompatState() {
        return getAppCompatState(/* ignoreVisibility= */ false);
    }

    /**
     * Same as {@link #getAppCompatState()} except when {@code ignoreVisibility} the visibility
     * of the activity is ignored.
     *
     * @param ignoreVisibility whether to ignore the visibility of the activity and not return
     *                         NOT_VISIBLE if {@code mVisibleRequested} is false.
     */
    private int getAppCompatState(boolean ignoreVisibility) {
        if (!ignoreVisibility && !mVisibleRequested) {
            return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
        }
        if (mInSizeCompatModeForBounds) {
            return true;
            return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
        }
        // Letterbox for fixed orientation. This check returns true only when an activity is
        // letterboxed for fixed orientation. Aspect ratio restrictions are also applied if
        // present. But this doesn't return true when the activity is letterboxed only because
        // of aspect ratio restrictions.
        if (isLetterboxedForFixedOrientationAndAspectRatio()) {
            return true;
            return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
        }
        // Letterbox for limited aspect ratio.
        return mIsAspectRatioApplied;
        if (mIsAspectRatioApplied) {
            return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
        }

        return APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
    }

    /**
+40 −0
Original line number Diff line number Diff line
@@ -40,6 +40,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
@@ -55,7 +60,9 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;

@@ -101,10 +108,14 @@ public class SizeCompatTests extends WindowTestsBase {

    private Task mTask;
    private ActivityRecord mActivity;
    private ActivityMetricsLogger mActivityMetricsLogger;
    private Properties mInitialConstrainDisplayApisFlags;

    @Before
    public void setUp() throws Exception {
        mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
        clearInvocations(mActivityMetricsLogger);
        doReturn(mActivityMetricsLogger).when(mAtm.mTaskSupervisor).getActivityMetricsLogger();
        mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties(
                NAMESPACE_CONSTRAIN_DISPLAY_APIS);
        DeviceConfig.setProperties(
@@ -1939,13 +1950,19 @@ public class SizeCompatTests extends WindowTestsBase {
        setUpDisplaySizeWithApp(1000, 2500);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());

        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
        rotateDisplay(mActivity.mDisplayContent, ROTATION_0);

        // After returning to the original rotation, bounds are computed in
@@ -1955,6 +1972,18 @@ public class SizeCompatTests extends WindowTestsBase {
        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);

        // After setting the visibility of the activity to false, areBoundsLetterboxed() still
        // returns true but the NOT_VISIBLE App Compat state is logged.
        mActivity.setVisibility(false);
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE);
        mActivity.setVisibility(true);
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
    }

    @Test
@@ -1963,12 +1992,15 @@ public class SizeCompatTests extends WindowTestsBase {
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
    }

    @Test
@@ -1978,12 +2010,15 @@ public class SizeCompatTests extends WindowTestsBase {
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
    }

    /**
@@ -2197,6 +2232,11 @@ public class SizeCompatTests extends WindowTestsBase {
                .isEqualTo(activity.getWindowConfiguration().getBounds());
    }

    private void verifyLogAppCompatState(ActivityRecord activity, int state) {
        verify(mActivityMetricsLogger, atLeastOnce()).logAppCompatState(
                argThat(r -> activity == r && r.getAppCompatState() == state));
    }

    static Configuration rotateDisplay(DisplayContent display, int rotation) {
        final Configuration c = new Configuration();
        display.getDisplayRotation().setRotation(rotation);