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

This commit is contained in:
Tom Natan
2021-08-24 15:53:07 +00:00
committed by Android (Google) Code Review
3 changed files with 227 additions and 3 deletions

View File

@@ -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.

View File

@@ -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;
}
/**

View File

@@ -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);