diff options
| -rw-r--r-- | core/api/current.txt | 22 | ||||
| -rw-r--r-- | core/java/android/app/jank/JankDataProcessor.java | 12 | ||||
| -rw-r--r-- | core/java/android/view/AttachedSurfaceControl.java | 21 | ||||
| -rw-r--r-- | core/java/android/view/FrameMetrics.java | 19 | ||||
| -rw-r--r-- | core/java/android/view/SurfaceControl.java | 164 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 16 | ||||
| -rw-r--r-- | core/java/android/window/flags/window_surfaces.aconfig | 9 | ||||
| -rw-r--r-- | core/java/com/android/internal/jank/FrameTracker.java | 14 | ||||
| -rw-r--r-- | core/jni/android_view_SurfaceControl.cpp | 42 |
9 files changed, 274 insertions, 45 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 4526de4de6e0..dd0c1d06a902 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -51406,6 +51406,7 @@ package android.view { method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl); method public default int getBufferTransformHint(); method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken(); + method @FlaggedApi("com.android.window.flags.jank_api") @NonNull public default android.view.SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.OnJankDataListener); method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public default void setChildBoundingInsets(@NonNull android.graphics.Rect); method public default void setTouchableRegion(@Nullable android.graphics.Region); @@ -51663,6 +51664,7 @@ package android.view { field public static final int DEADLINE = 13; // 0xd field public static final int DRAW_DURATION = 4; // 0x4 field public static final int FIRST_DRAW_FRAME = 9; // 0x9 + field @FlaggedApi("com.android.window.flags.jank_api") public static final int FRAME_TIMELINE_VSYNC_ID = 14; // 0xe field public static final int GPU_DURATION = 12; // 0xc field public static final int INPUT_HANDLING_DURATION = 1; // 0x1 field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa @@ -53100,6 +53102,26 @@ package android.view { method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl); } + @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.JankData { + method public long getActualAppFrameTimeNanos(); + method public int getJankType(); + method public long getScheduledAppFrameTimeNanos(); + method public long getVsyncId(); + field public static final int JANK_APPLICATION = 2; // 0x2 + field public static final int JANK_COMPOSER = 1; // 0x1 + field public static final int JANK_NONE = 0; // 0x0 + field public static final int JANK_OTHER = 4; // 0x4 + } + + @FlaggedApi("com.android.window.flags.jank_api") public static interface SurfaceControl.OnJankDataListener { + method public void onJankDataAvailable(@NonNull java.util.List<android.view.SurfaceControl.JankData>); + } + + @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.OnJankDataListenerRegistration { + method public void flush(); + method public void removeAfter(long); + } + public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable { ctor public SurfaceControl.Transaction(); method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener); diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java index 3783a5f9e829..7525d0402ee4 100644 --- a/core/java/android/app/jank/JankDataProcessor.java +++ b/core/java/android/app/jank/JankDataProcessor.java @@ -70,8 +70,8 @@ public class JankDataProcessor { for (int j = 0; j < mPendingStates.size(); j++) { StateData pendingState = mPendingStates.get(j); // This state was active during the frame - if (frame.frameVsyncId >= pendingState.mVsyncIdStart - && frame.frameVsyncId <= pendingState.mVsyncIdEnd) { + if (frame.getVsyncId() >= pendingState.mVsyncIdStart + && frame.getVsyncId() <= pendingState.mVsyncIdEnd) { recordFrameCount(frame, pendingState, activityName, appUid); pendingState.mProcessed = true; @@ -131,14 +131,14 @@ public class JankDataProcessor { mPendingJankStats.put(stateData.mStateDataKey, jankStats); } // This state has already been accounted for - if (jankStats.processedVsyncId == frameData.frameVsyncId) return; + if (jankStats.processedVsyncId == frameData.getVsyncId()) return; jankStats.mTotalFrames += 1; - if (frameData.jankType == JankData.JANK_APPLICATION) { + if ((frameData.getJankType() & JankData.JANK_APPLICATION) != 0) { jankStats.mJankyFrames += 1; } - jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs); - jankStats.processedVsyncId = frameData.frameVsyncId; + jankStats.recordFrameOverrun(frameData.getActualAppFrameTimeNanos()); + jankStats.processedVsyncId = frameData.getVsyncId(); } diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index 5406cf557410..264db4a604ff 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -15,9 +15,11 @@ */ package android.view; +import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.UiThread; import android.content.Context; import android.graphics.Rect; @@ -29,6 +31,8 @@ import android.window.SurfaceSyncGroup; import com.android.window.flags.Flags; +import java.util.concurrent.Executor; + /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This * is used in combination with the {@link android.view.SurfaceControl} API to enable @@ -202,4 +206,21 @@ public interface AttachedSurfaceControl { throw new UnsupportedOperationException("The getInputTransferToken needs to be " + "implemented before making this call."); } + + /** + * Registers a {@link OnJankDataListener} to receive jank classification data about rendered + * frames. + * + * @param executor The executor on which the listener will be invoked. + * @param listener The listener to add. + * @return The {@link OnJankDataListenerRegistration} for the listener. + */ + @NonNull + @FlaggedApi(Flags.FLAG_JANK_API) + @SuppressLint("PairedRegistration") + default SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull SurfaceControl.OnJankDataListener listener) { + return SurfaceControl.OnJankDataListenerRegistration.NONE; + } } diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index 9e25a3e18c0c..58b2a67ec69e 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -18,10 +18,13 @@ package android.view; import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import com.android.window.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -177,6 +180,16 @@ public final class FrameMetrics { public static final int DEADLINE = 13; /** + * Metric identifier for the frame's VSync identifier. + * <p> + * The id that corresponds to the chosen frame timeline, used to correlate a frame produced + * by HWUI with the timeline data from the compositor. + * </p> + */ + @FlaggedApi(Flags.FLAG_JANK_API) + public static final int FRAME_TIMELINE_VSYNC_ID = 14; + + /** * Identifiers for metrics available for each frame. * * {@see #getMetric(int)} @@ -337,7 +350,8 @@ public final class FrameMetrics { * @return the value of the metric or -1 if it is not available. */ public long getMetric(@Metric int id) { - if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) { + if (id < UNKNOWN_DELAY_DURATION + || id > (Flags.jankApi() ? FRAME_TIMELINE_VSYNC_ID : DEADLINE)) { return -1; } @@ -351,6 +365,8 @@ public final class FrameMetrics { return mTimingData[Index.INTENDED_VSYNC]; } else if (id == VSYNC_TIMESTAMP) { return mTimingData[Index.VSYNC]; + } else if (id == FRAME_TIMELINE_VSYNC_ID) { + return mTimingData[Index.FRAME_TIMELINE_VSYNC_ID]; } int durationsIdx = 2 * id; @@ -358,4 +374,3 @@ public final class FrameMetrics { - mTimingData[DURATIONS[durationsIdx]]; } } - diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 3dce95e7a5cc..68efa79715a3 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -411,8 +411,19 @@ public final class SurfaceControl implements Parcelable { /** * Jank information to be fed back via {@link OnJankDataListener}. - * @hide + * <p> + * Apps may register a {@link OnJankDataListener} to get periodic batches of jank classification + * data from the (<a + * href="https://source.android.com/docs/core/graphics/surfaceflinger-windowmanagersystem"> + * composer</a> regarding rendered frames. A frame is considered janky if it did not reach the + * display at the intended time, typically due to missing a rendering deadline. This API + * provides information that can be used to identify the root cause of the scheduling misses + * and provides overall frame scheduling statistics. + * <p> + * This API can be used in conjunction with the {@link FrameMetrics} API by associating jank + * classification data with {@link FrameMetrics} data via the frame VSync id. */ + @FlaggedApi(Flags.FLAG_JANK_API) public static class JankData { /** @@ -429,29 +440,105 @@ public final class SurfaceControl implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface JankType {} - // No Jank + /** + * No jank detected, the frame was on time. + */ public static final int JANK_NONE = 0; - // Jank caused by the composer missing a deadline + + /** + * Bitmask for jank due to deadlines missed by the composer. + */ public static final int JANK_COMPOSER = 1 << 0; - // Jank caused by the application missing the composer's deadline + + /** + * Bitmask for jank due to deadlines missed by the application. + */ public static final int JANK_APPLICATION = 1 << 1; - // Jank due to other unknown reasons + + /** + * Bitmask for jank due to deadlines missed by other system components. + */ public static final int JANK_OTHER = 1 << 2; + private final long mFrameVsyncId; + private final @JankType int mJankType; + private final long mFrameIntervalNs; + private final long mScheduledAppFrameTimeNs; + private final long mActualAppFrameTimeNs; + + /** + * @hide + */ public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs, long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) { - this.frameVsyncId = frameVsyncId; - this.jankType = jankType; - this.frameIntervalNs = frameIntervalNs; - this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs; - this.actualAppFrameTimeNs = actualAppFrameTimeNs; - } - - public final long frameVsyncId; - public final @JankType int jankType; - public final long frameIntervalNs; - public final long scheduledAppFrameTimeNs; - public final long actualAppFrameTimeNs; + mFrameVsyncId = frameVsyncId; + mJankType = jankType; + mFrameIntervalNs = frameIntervalNs; + mScheduledAppFrameTimeNs = scheduledAppFrameTimeNs; + mActualAppFrameTimeNs = actualAppFrameTimeNs; + } + + /** + * Returns the id of the frame for this jank classification. + * + * @see FrameMetrics#FRAME_TIMELINE_VSYNC_ID + * @see Choreographer.FrameTimeline#getVsyncId + * @see Transaction#setFrameTimeline + * @return the frame id + */ + public long getVsyncId() { + return mFrameVsyncId; + } + + /** + * Returns the bitmask indicating the types of jank observed. + * + * @return the jank type bitmask + */ + public @JankType int getJankType() { + return mJankType; + } + + /** + * Returns the time between frame VSyncs in nanoseconds. + * + * @return the frame interval in ns + * @hide + */ + public long getFrameIntervalNanos() { + return mFrameIntervalNs; + } + + /** + * Returns the duration in nanoseconds the application was scheduled to use to render this + * frame. + * <p> + * Note that this may be higher than the frame interval to allow for CPU/GPU + * parallelization of work. + * + * @return scheduled app time in ns + */ + public long getScheduledAppFrameTimeNanos() { + return mScheduledAppFrameTimeNs; + } + + /** + * Returns the actual time in nanoseconds taken by the application to render this frame. + * + * @return the actual app time in ns + */ + public long getActualAppFrameTimeNanos() { + return mActualAppFrameTimeNs; + } + + @Override + public String toString() { + return "JankData{vsync=" + mFrameVsyncId + + ", jankType=0x" + Integer.toHexString(mJankType) + + ", frameInterval=" + mFrameIntervalNs + "ns" + + ", scheduledAppTime=" + mScheduledAppFrameTimeNs + "ns" + + ", actualAppTime=" + mActualAppFrameTimeNs + "ns}"; + } } /** @@ -459,12 +546,13 @@ public final class SurfaceControl implements Parcelable { * surface. * * @see JankData - * @see #addJankDataListener - * @hide + * @see #addOnJankDataListener */ + @FlaggedApi(Flags.FLAG_JANK_API) public interface OnJankDataListener { /** - * Called when new jank classifications are available. + * Called when new jank classifications are available. The listener is invoked out of band + * of the rendered frames with jank classification data for a batch of frames. */ void onJankDataAvailable(@NonNull List<JankData> jankData); @@ -472,9 +560,22 @@ public final class SurfaceControl implements Parcelable { /** * Handle to a registered {@link OnJankDatalistener}. - * @hide */ + @FlaggedApi(Flags.FLAG_JANK_API) public static class OnJankDataListenerRegistration { + /** @hide */ + public static final OnJankDataListenerRegistration NONE = + new OnJankDataListenerRegistration() { + @Override + public void flush() {} + + @Override + public void removeAfter(long afterVsync) {} + + @Override + public void release() {} + }; + private final long mNativeObject; private static final NativeAllocationRegistry sRegistry = @@ -485,6 +586,11 @@ public final class SurfaceControl implements Parcelable { private final Runnable mFreeNativeResources; private boolean mRemoved = false; + private OnJankDataListenerRegistration() { + mNativeObject = 0; + mFreeNativeResources = () -> {}; + } + OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) { mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener); mFreeNativeResources = (mNativeObject == 0) ? () -> {} : @@ -500,10 +606,17 @@ public final class SurfaceControl implements Parcelable { } /** - * Request the removal of the registered listener after the VSync with the provided ID. Use - * a value <= 0 for afterVsync to remove the listener immediately. The given listener will - * not be removed before the given VSync, but may still reveive data for frames past the - * provided VSync. + * Schedule the removal of the registered listener after the frame with the provided id. + * <p> + * Because jank classification is only possible after frames have been displayed, the + * callbacks are always delayed. To ensure receipt of all jank classification data, an + * application can schedule the removal to happen no sooner than after the data for the + * frame with the provided id has been provided. + * <p> + * Use a value <= 0 for afterVsync to remove the listener immediately, ensuring no future + * callbacks. + * + * @param afterVsync the id of the Vsync after which to remove the listener */ public void removeAfter(long afterVsync) { mRemoved = true; @@ -512,6 +625,7 @@ public final class SurfaceControl implements Parcelable { /** * Free the native resources associated with the listener registration. + * @hide */ public void release() { if (!mRemoved) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 482291881496..e50662adc3f1 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -140,6 +140,8 @@ import android.accessibilityservice.AccessibilityService; import android.animation.AnimationHandler; import android.animation.LayoutTransition; import android.annotation.AnyThread; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; @@ -11899,6 +11901,20 @@ public final class ViewRootImpl implements ViewParent, } /** + * {@inheritDoc} + */ + @NonNull + @Override + @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API) + public SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull SurfaceControl.OnJankDataListener listener) { + SurfaceControl.OnJankDataListener wrapped = (data) -> + executor.execute(() -> listener.onJankDataAvailable(data)); + return mSurfaceControl.addOnJankDataListener(wrapped); + } + + /** * Class for managing the accessibility interaction connection * based on the global accessibility state. */ diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 392c307de7ba..96b9dc7cab0e 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -97,3 +97,12 @@ flag { is_fixed_read_only: true bug: "308662081" } + +flag { + name: "jank_api" + namespace: "window_surfaces" + description: "Adds the jank data listener to AttachedSurfaceControl" + is_fixed_read_only: true + is_exported: true + bug: "293949943" +} diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 44c0bd01d545..2834e6883316 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -139,7 +139,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai } static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) { - return new JankInfo(jankStat.frameVsyncId).update(jankStat); + return new JankInfo(jankStat.getVsyncId()).update(jankStat); } private JankInfo(long frameVsyncId) { @@ -154,10 +154,10 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai private JankInfo update(SurfaceControl.JankData jankStat) { this.surfaceControlCallbackFired = true; - this.jankType = jankStat.jankType; - this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs); + this.jankType = jankStat.getJankType(); + this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.getFrameIntervalNanos()); if (Flags.useSfFrameDuration()) { - this.totalDurationNanos = jankStat.actualAppFrameTimeNs; + this.totalDurationNanos = jankStat.getActualAppFrameTimeNanos(); } return this; } @@ -458,14 +458,14 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai } for (SurfaceControl.JankData jankStat : jankData) { - if (!isInRange(jankStat.frameVsyncId)) { + if (!isInRange(jankStat.getVsyncId())) { continue; } - JankInfo info = findJankInfo(jankStat.frameVsyncId); + JankInfo info = findJankInfo(jankStat.getVsyncId()); if (info != null) { info.update(jankStat); } else { - mJankInfos.put((int) jankStat.frameVsyncId, + mJankInfos.put((int) jankStat.getVsyncId(), JankInfo.createFromSurfaceControlCallback(jankStat)); } } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index d3bf36e60345..82b463ec9091 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -2163,7 +2163,7 @@ static void nativeClearTrustedPresentationCallback(JNIEnv* env, jclass clazz, jl class JankDataListenerWrapper : public JankDataListener { public: - JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) { + JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) : mRemovedVsyncId(-1) { mOnJankDataListenerWeak = env->NewWeakGlobalRef(onJankDataListenerObject); env->GetJavaVM(&mVm); } @@ -2174,6 +2174,12 @@ public: } bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override { + // Don't invoke the listener if we've been force removed and got this + // out-of-order callback. + if (mRemovedVsyncId == 0) { + return false; + } + JNIEnv* env = getEnv(); jobject target = env->NewLocalRef(mOnJankDataListenerWeak); @@ -2181,9 +2187,29 @@ public: return false; } - jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(), - gJankDataClassInfo.clazz, nullptr); - for (size_t i = 0; i < jankData.size(); i++) { + // Compute the count of data items we'll actually forward to Java. + size_t count = 0; + if (mRemovedVsyncId <= 0) { + count = jankData.size(); + } else { + for (const gui::JankData& frame : jankData) { + if (frame.frameVsyncId <= mRemovedVsyncId) { + count++; + } + } + } + + if (count == 0) { + return false; + } + + jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr); + for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) { + // Filter any data for frames past our removal vsync. + if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) { + continue; + } + // The exposed constants in SurfaceControl are simplified, so we need to translate the // jank type we get from SF to what is exposed in Java. int sfJankType = jankData[i].jankType; @@ -2210,7 +2236,7 @@ public: jankData[i].frameVsyncId, javaJankType, jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs, jankData[i].actualAppFrameTimeNs); - env->SetObjectArrayElement(jJankDataArray, i, jJankData); + env->SetObjectArrayElement(jJankDataArray, j++, jJankData); env->DeleteLocalRef(jJankData); } @@ -2225,6 +2251,11 @@ public: return true; } + void removeListener(int64_t afterVsyncId) { + mRemovedVsyncId = (afterVsyncId <= 0) ? 0 : afterVsyncId; + JankDataListener::removeListener(afterVsyncId); + } + private: JNIEnv* getEnv() { @@ -2235,6 +2266,7 @@ private: JavaVM* mVm; jobject mOnJankDataListenerWeak; + int64_t mRemovedVsyncId; }; static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz, |