diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java new file mode 100644 index 0000000000000..fd5846847c950 --- /dev/null +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.jank; + +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__FHD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__HD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__QHD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__SD; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__UNKNOWN_RESOLUTION; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; +import android.os.Handler; +import android.util.SparseArray; +import android.view.DisplayInfo; + +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** +* A class that tracks the display resolutions. +* @hide +*/ +public class DisplayResolutionTracker { + private static final String TAG = DisplayResolutionTracker.class.getSimpleName(); + + public static final int RESOLUTION_UNKNOWN = + UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__UNKNOWN_RESOLUTION; + public static final int RESOLUTION_SD = + UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__SD; + public static final int RESOLUTION_HD = + UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__HD; + public static final int RESOLUTION_FHD = + UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__FHD; + public static final int RESOLUTION_QHD = + UIINTERACTION_FRAME_INFO_REPORTED__DISPLAY_RESOLUTION__QHD; + + /** @hide */ + @IntDef({ + RESOLUTION_UNKNOWN, + RESOLUTION_SD, + RESOLUTION_HD, + RESOLUTION_FHD, + RESOLUTION_QHD, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Resolution { + } + + private final DisplayInterface mManager; + private final SparseArray mResolutions = new SparseArray<>(); + private final Object mLock = new Object(); + + public DisplayResolutionTracker(@Nullable Handler handler) { + this(DisplayInterface.getDefault(handler)); + } + + @VisibleForTesting + public DisplayResolutionTracker(DisplayInterface manager) { + mManager = manager; + mManager.registerDisplayListener(new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + updateDisplay(displayId); + } + + @Override + public void onDisplayChanged(int displayId) { + updateDisplay(displayId); + } + + @Override + public void onDisplayRemoved(int displayId) { + // Not in the event mask below, won't be called. + } + }); + } + + private void updateDisplay(int displayId) { + DisplayInfo info = mManager.getDisplayInfo(displayId); + @Resolution int resolution = getResolution(info); + + synchronized (mLock) { + mResolutions.put(displayId, resolution); + } + } + + /** + * Returns the (cached) resolution of the display with the given ID. + */ + @Resolution + public int getResolution(int displayId) { + return mResolutions.get(displayId, RESOLUTION_UNKNOWN); + } + + /** + * Returns the resolution of the given {@link DisplayInfo}. + */ + @VisibleForTesting + @Resolution + public static int getResolution(DisplayInfo info) { + int smaller = Math.min(info.logicalWidth, info.logicalHeight); + int larger = Math.max(info.logicalWidth, info.logicalHeight); + if (smaller < 720 || larger < 1280) { + return RESOLUTION_SD; + } else if (smaller < 1080 || larger < 1920) { + return RESOLUTION_HD; + } else if (smaller < 1440 || larger < 2560) { + return RESOLUTION_FHD; + } else { + return RESOLUTION_QHD; + } + } + + /** + * Wrapper around the final {@link DisplayManagerGlobal} class. + * @hide + */ + @VisibleForTesting + public interface DisplayInterface { + /** Reurns an implementation wrapping {@link DisplayManagerGlobal}. */ + static DisplayInterface getDefault(@Nullable Handler handler) { + DisplayManagerGlobal manager = DisplayManagerGlobal.getInstance(); + return new DisplayInterface() { + @Override + public void registerDisplayListener(DisplayManager.DisplayListener listener) { + manager.registerDisplayListener(listener, handler, + DisplayManager.EVENT_FLAG_DISPLAY_ADDED + | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); + } + + @Override + public DisplayInfo getDisplayInfo(int displayId) { + return manager.getDisplayInfo(displayId); + } + }; + } + + /** {@see DisplayManagerGlobal#registerDisplayListener} */ + void registerDisplayListener(DisplayManager.DisplayListener listener); + /** {@see DisplayManagerGlobal#getDisplayInfo} */ + DisplayInfo getDisplayInfo(int displayId); + } +} diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 48343803b10e9..e4195d29c1ee0 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -100,6 +100,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private final Session mSession; private final ViewRootWrapper mViewRoot; private final SurfaceControlWrapper mSurfaceControlWrapper; + private final int mDisplayId; private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback; private final Handler mHandler; private final ChoreographerWrapper mChoreographer; @@ -213,6 +214,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; mListener = listener; + mDisplayId = config.getDisplayId(); if (mSurfaceOnly) { mSurfaceControl = config.getSurfaceControl(); @@ -625,6 +627,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (mSession.logToStatsd()) { mStatsLog.write( FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED, + mDisplayId, mSession.getStatsdInteractionType(), totalFramesCount, missedFramesCount, @@ -785,9 +788,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } public static class StatsLogWrapper { - public void write(int code, + private final DisplayResolutionTracker mDisplayResolutionTracker; + + public StatsLogWrapper(DisplayResolutionTracker displayResolutionTracker) { + mDisplayResolutionTracker = displayResolutionTracker; + } + + /** {@see FrameworkStatsLog#write) */ + public void write(int code, int displayId, int arg1, long arg2, long arg3, long arg4, long arg5, long arg6, long arg7) { - FrameworkStatsLog.write(code, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + FrameworkStatsLog.write(code, arg1, arg2, arg3, arg4, arg5, arg6, arg7, + mDisplayResolutionTracker.getResolution(displayId)); } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index fc4e041058b1d..b5991b3fcbdc2 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -306,6 +306,7 @@ public class InteractionJankMonitor { @GuardedBy("mLock") private final SparseArray mTimeoutActions; private final HandlerThread mWorker; + private final DisplayResolutionTracker mDisplayResolutionTracker; private final Object mLock = new Object(); private volatile boolean mEnabled = DEFAULT_ENABLED; @@ -408,6 +409,7 @@ public class InteractionJankMonitor { mWorker = worker; mWorker.start(); mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; + mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler()); // Post initialization to the background in case we're running on the main // thread. @@ -443,7 +445,8 @@ public class InteractionJankMonitor { final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper(); return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot, - surfaceControl, choreographer, frameMetrics, new FrameTracker.StatsLogWrapper(), + surfaceControl, choreographer, frameMetrics, + new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker), mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener, config); } @@ -1098,6 +1101,14 @@ public class InteractionJankMonitor { public Handler getHandler() { return mHandler; } + + /** + * @return the ID of the display this interaction in on. + */ + @VisibleForTesting + public int getDisplayId() { + return (mSurfaceOnly ? mContext.getDisplay() : mView.getDisplay()).getDisplayId(); + } } /** diff --git a/core/tests/coretests/src/com/android/internal/jank/DisplayResolutionTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/DisplayResolutionTrackerTest.java new file mode 100644 index 0000000000000..50210226db6fa --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/jank/DisplayResolutionTrackerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.jank; + +import static com.android.internal.jank.DisplayResolutionTracker.getResolution; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.display.DisplayManager; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +@SmallTest +public class DisplayResolutionTrackerTest { + private static final DisplayInfo SD = makeDisplayInfo(800, 600); + private static final DisplayInfo HD = makeDisplayInfo(720, 1280); + private static final DisplayInfo FHD = makeDisplayInfo(2340, 1080); + private static final DisplayInfo QHD = makeDisplayInfo(3120, 1440); + + private DisplayResolutionTracker.DisplayInterface mDisplayManager; + private ArgumentCaptor mListenerCaptor; + private DisplayResolutionTracker mTracker; + + @Before + public void setup() throws Exception { + mDisplayManager = mock(DisplayResolutionTracker.DisplayInterface.class); + mListenerCaptor = ArgumentCaptor.forClass(DisplayManager.DisplayListener.class); + + mTracker = new DisplayResolutionTracker(mDisplayManager); + + verify(mDisplayManager).registerDisplayListener(mListenerCaptor.capture()); + } + + @Test + public void testResolutionMapping() { + assertThat(getResolution(SD)).isEqualTo(DisplayResolutionTracker.RESOLUTION_SD); + assertThat(getResolution(HD)).isEqualTo(DisplayResolutionTracker.RESOLUTION_HD); + assertThat(getResolution(FHD)).isEqualTo(DisplayResolutionTracker.RESOLUTION_FHD); + assertThat(getResolution(QHD)).isEqualTo(DisplayResolutionTracker.RESOLUTION_QHD); + } + + @Test + public void testResolutionUpdatesOnDisplayChanges() throws Exception { + assertThat(mTracker.getResolution(42)) + .isEqualTo(DisplayResolutionTracker.RESOLUTION_UNKNOWN); + + when(mDisplayManager.getDisplayInfo(42)).thenReturn(FHD, QHD); + + mListenerCaptor.getValue().onDisplayAdded(42); + assertThat(mTracker.getResolution(42)) + .isEqualTo(DisplayResolutionTracker.RESOLUTION_FHD); + mListenerCaptor.getValue().onDisplayChanged(42); + assertThat(mTracker.getResolution(42)) + .isEqualTo(DisplayResolutionTracker.RESOLUTION_QHD); + } + + private static DisplayInfo makeDisplayInfo(int width, int height) { + DisplayInfo info = new DisplayInfo(); + info.logicalWidth = width; + info.logicalHeight = height; + return info; + } +} diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index e068730e9bda3..dbbd70525d281 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -124,6 +124,7 @@ public class FrameTrackerTest { when(config.isSurfaceOnly()).thenReturn(surfaceOnly); when(config.getSurfaceControl()).thenReturn(mSurfaceControl); when(config.shouldDeferMonitor()).thenReturn(true); + when(config.getDisplayId()).thenReturn(42); View view = mRule.getActivity().getWindow().getDecorView(); Handler spyHandler = spy(new Handler(handler.getLooper())); when(config.getView()).thenReturn(surfaceOnly ? null : view); @@ -168,6 +169,7 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); verify(tracker, never()).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, @@ -204,6 +206,7 @@ public class FrameTrackerTest { verify(tracker).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, @@ -240,6 +243,7 @@ public class FrameTrackerTest { verify(tracker, never()).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, @@ -276,6 +280,7 @@ public class FrameTrackerTest { verify(tracker).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, @@ -315,6 +320,7 @@ public class FrameTrackerTest { verify(tracker).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, @@ -356,6 +362,7 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); verify(tracker, never()).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, @@ -483,6 +490,7 @@ public class FrameTrackerTest { verify(tracker).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, @@ -519,6 +527,7 @@ public class FrameTrackerTest { verify(tracker, never()).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, @@ -555,6 +564,7 @@ public class FrameTrackerTest { verify(tracker, never()).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, @@ -585,6 +595,7 @@ public class FrameTrackerTest { verify(mSurfaceControlWrapper).removeJankStatsListener(any()); verify(tracker).triggerPerfetto(); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), + eq(42), /* displayId */ eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), eq(6L) /* totalFrames */, eq(5L) /* missedFrames */, diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 1519e48adbe0b..4770fa6315aa9 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -354,10 +354,11 @@ public class InteractionJankMonitorTest { when(configuration.isSurfaceOnly()).thenReturn(false); when(configuration.getView()).thenReturn(mView); when(configuration.getHandler()).thenReturn(mView.getHandler()); + when(configuration.getDisplayId()).thenReturn(42); FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(), threadedRenderer, viewRoot, surfaceControl, choreographer, - new FrameMetricsWrapper(), new StatsLogWrapper(), + new FrameMetricsWrapper(), new StatsLogWrapper(null), /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, listener, configuration));