That fuck shit the fascists are using
1package org.tm.archive.util;
2
3import android.app.Application;
4import android.content.Context;
5import android.view.Choreographer;
6import android.view.Display;
7
8import androidx.annotation.NonNull;
9
10import org.signal.core.util.logging.Log;
11
12import java.util.Locale;
13import java.util.concurrent.TimeUnit;
14
15/**
16 * Tracks the frame rate of the app and logs when things are bad.
17 *
18 * In general, whenever alterations are made here, the author should be very cautious to do as
19 * little work as possible, because we don't want the tracker itself to impact the frame rate.
20 */
21public class FrameRateTracker {
22
23 private static final String TAG = Log.tag(FrameRateTracker.class);
24
25 private static final int MAX_CONSECUTIVE_FRAME_LOGS = 10;
26
27 private final Application context;
28
29 private double refreshRate;
30 private long idealTimePerFrameNanos;
31 private long badFrameThresholdNanos;
32
33 private long lastFrameTimeNanos;
34
35 private long consecutiveFrameWarnings;
36
37 public FrameRateTracker(@NonNull Application application) {
38 this.context = application;
39
40 updateRefreshRate();
41 }
42
43 public void start() {
44 Log.d(TAG, String.format(Locale.ENGLISH, "Beginning frame rate tracking. Screen refresh rate: %.2f hz, or %.2f ms per frame.", refreshRate, idealTimePerFrameNanos / (float) 1_000_000));
45
46 lastFrameTimeNanos = System.nanoTime();
47
48 Choreographer.getInstance().postFrameCallback(calculator);
49 }
50
51 public void stop() {
52 Choreographer.getInstance().removeFrameCallback(calculator);
53 }
54
55 /**
56 * The natural screen refresh rate, in hertz. May not always return the same value if a display
57 * has a dynamic refresh rate.
58 */
59 public static float getDisplayRefreshRate(@NonNull Context context) {
60 Display display = ServiceUtil.getWindowManager(context).getDefaultDisplay();
61 return display.getRefreshRate();
62 }
63
64 /**
65 * Displays with dynamic refresh rates may change their reported refresh rate over time.
66 */
67 private void updateRefreshRate() {
68 double newRefreshRate = getDisplayRefreshRate(context);
69
70 if (this.refreshRate != newRefreshRate) {
71 if (this.refreshRate > 0) {
72 Log.d(TAG, String.format(Locale.ENGLISH, "Refresh rate changed from %.2f hz to %.2f hz", refreshRate, newRefreshRate));
73 }
74
75 this.refreshRate = getDisplayRefreshRate(context);
76 this.idealTimePerFrameNanos = (long) (TimeUnit.SECONDS.toNanos(1) / refreshRate);
77 this.badFrameThresholdNanos = idealTimePerFrameNanos * (int) (refreshRate / 4);
78 }
79 }
80
81 private final Choreographer.FrameCallback calculator = new Choreographer.FrameCallback() {
82 @Override
83 public void doFrame(long frameTimeNanos) {
84 long elapsedNanos = frameTimeNanos - lastFrameTimeNanos;
85 double fps = TimeUnit.SECONDS.toNanos(1) / (double) elapsedNanos;
86
87 if (elapsedNanos > badFrameThresholdNanos) {
88 if (consecutiveFrameWarnings < MAX_CONSECUTIVE_FRAME_LOGS) {
89 long droppedFrames = elapsedNanos / idealTimePerFrameNanos;
90 Log.w(TAG, String.format(Locale.ENGLISH, "Bad frame! Took %d ms (%d dropped frames, or %.2f FPS)", TimeUnit.NANOSECONDS.toMillis(elapsedNanos), droppedFrames, fps));
91 consecutiveFrameWarnings++;
92 }
93 } else {
94 consecutiveFrameWarnings = 0;
95 }
96
97 lastFrameTimeNanos = frameTimeNanos;
98 Choreographer.getInstance().postFrameCallback(this);
99 }
100 };
101}