Loading services/core/java/com/android/server/wm/ActivityMetricsLogger.java +139 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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); } } /** Loading Loading @@ -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. Loading services/core/java/com/android/server/wm/ActivityRecord.java +48 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -7438,6 +7444,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } logAppCompatState(); } /** Loading @@ -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; } /** Loading services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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); } /** Loading Loading @@ -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); Loading Loading
services/core/java/com/android/server/wm/ActivityMetricsLogger.java +139 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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(); Loading Loading @@ -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; Loading Loading @@ -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); } } /** Loading Loading @@ -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. Loading
services/core/java/com/android/server/wm/ActivityRecord.java +48 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -7438,6 +7444,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); } logAppCompatState(); } /** Loading @@ -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; } /** Loading
services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +40 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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( Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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); } /** Loading Loading @@ -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); Loading