blob: 279ac6149c2f37c4508ea430283e406015adec0c [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.jank_tracker;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build.VERSION_CODES;
import android.view.Display;
import android.view.FrameMetrics;
import android.view.Window;
import android.view.Window.OnFrameMetricsAvailableListener;
import androidx.annotation.RequiresApi;
import org.chromium.base.ContextUtils;
import org.chromium.base.TimeUtils;
import org.chromium.base.TraceEvent;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class receives OnFrameMetricsAvailableListener.onFrameMetricsAvailable() callbacks and
* records frame durations in a FrameMetricsStore instance.
*/
@RequiresApi(api = VERSION_CODES.N)
public class FrameMetricsListener implements OnFrameMetricsAvailableListener {
private class DisplayListenerBackend implements DisplayListener {
public void startListening() {
Context appCtx = ContextUtils.getApplicationContext();
DisplayManager displayManager =
(DisplayManager) appCtx.getSystemService(Context.DISPLAY_SERVICE);
displayManager.registerDisplayListener(this, /* handler= */ null);
}
@Override
public void onDisplayAdded(int sdkDisplayId) {}
@Override
public void onDisplayRemoved(int sdkDisplayId) {}
@Override
public void onDisplayChanged(int sdkDisplayId) {
maybeUpdateRefreshRate();
}
}
private DisplayListenerBackend mBackend = new DisplayListenerBackend();
private final FrameMetricsStore mFrameMetricsStore;
private AtomicBoolean mIsRecording = new AtomicBoolean(false);
// Microseconds between each frame.
private long mVsyncInterval;
public FrameMetricsListener(FrameMetricsStore frameMetricsStore) {
mFrameMetricsStore = frameMetricsStore;
mBackend.startListening();
maybeUpdateRefreshRate();
}
private void maybeUpdateRefreshRate() {
Context appCtx = ContextUtils.getApplicationContext();
DisplayManager displayManager =
(DisplayManager) appCtx.getSystemService(Context.DISPLAY_SERVICE);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
if (display == null) {
return;
}
float refreshRate = display.getRefreshRate();
final long kMicrosecondsPerSecond = 1000_000L;
mVsyncInterval = kMicrosecondsPerSecond / ((long) refreshRate);
TraceEvent.instant(
"FrameMetricsListener.maybeUpdateRefreshRate", Long.toString(mVsyncInterval));
}
/**
* Toggles recording into FrameMetricsStore. When recording is stopped, reports accumulated
* metrics.
* @param isRecording
*/
public void setIsListenerRecording(boolean isRecording) {
mIsRecording.set(isRecording);
}
@RequiresApi(api = VERSION_CODES.N)
@Override
public void onFrameMetricsAvailable(
Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
if (!mIsRecording.get()) {
return;
}
long frameTotalDurationNs = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
long frame_start_vsync_ts = frameMetrics.getMetric(FrameMetrics.VSYNC_TIMESTAMP);
try (TraceEvent e =
TraceEvent.scoped("onFrameMetricsAvailable", Long.toString(frameTotalDurationNs))) {
// FrameMetrics.DEADLINE was added in API level 31(S).
// TODO(b/311139161): Update RequiresApi level to Android S.
long deadlineNs = frameMetrics.getMetric(FrameMetrics.DEADLINE);
int missedVsyncs = 0;
if (frameTotalDurationNs >= deadlineNs) {
long frameDeadlineDeltaUs =
(frameTotalDurationNs - deadlineNs) / TimeUtils.NANOSECONDS_PER_MICROSECOND;
missedVsyncs = (int) ((frameDeadlineDeltaUs + mVsyncInterval) / mVsyncInterval);
}
mFrameMetricsStore.addFrameMeasurement(
frameTotalDurationNs, missedVsyncs, frame_start_vsync_ts);
}
}
}