That fuck shit the fascists are using
1package org.tm.archive.util;
2
3import android.app.Application;
4import android.os.Handler;
5import android.os.Looper;
6
7import androidx.annotation.MainThread;
8import androidx.annotation.NonNull;
9
10import org.signal.core.util.Stopwatch;
11import org.signal.core.util.concurrent.SignalExecutors;
12import org.signal.core.util.logging.Log;
13
14import java.util.LinkedList;
15import java.util.List;
16
17/**
18 * Manages our app startup flow.
19 */
20public final class AppStartup {
21
22 /** The time to wait after Application#onCreate() to see if any UI rendering starts */
23 private final long UI_WAIT_TIME = 500;
24
25 /** The maximum amount of time we'll wait for critical rendering events to finish. */
26 private final long FAILSAFE_RENDER_TIME = 2500;
27
28 private static final String TAG = Log.tag(AppStartup.class);
29
30 private static final AppStartup INSTANCE = new AppStartup();
31
32 private final List<Task> blocking;
33 private final List<Task> nonBlocking;
34 private final List<Task> postRender;
35 private final Handler postRenderHandler;
36
37 private int outstandingCriticalRenderEvents;
38
39 private long applicationStartTime;
40 private long renderStartTime;
41 private long renderEndTime;
42
43 public static @NonNull AppStartup getInstance() {
44 return INSTANCE;
45 }
46
47 private AppStartup() {
48 this.blocking = new LinkedList<>();
49 this.nonBlocking = new LinkedList<>();
50 this.postRender = new LinkedList<>();
51 this.postRenderHandler = new Handler(Looper.getMainLooper());
52 }
53
54 public void onApplicationCreate() {
55 this.applicationStartTime = System.currentTimeMillis();
56 }
57
58 /**
59 * Schedules a task that must happen during app startup in a blocking fashion.
60 */
61 @MainThread
62 public @NonNull AppStartup addBlocking(@NonNull String name, @NonNull Runnable task) {
63 blocking.add(new Task(name, task));
64 return this;
65 }
66
67 /**
68 * Schedules a task that should not block app startup, but should still happen as quickly as
69 * possible.
70 */
71 @MainThread
72 public @NonNull AppStartup addNonBlocking(@NonNull Runnable task) {
73 nonBlocking.add(new Task("", task));
74 return this;
75 }
76
77 /**
78 * Schedules a task that should only be executed after all critical UI has been rendered. If no
79 * UI will be shown (i.e. the Application was created in the background), this will simply happen
80 * a short delay after {@link Application#onCreate()}.
81 * @param task
82 * @return
83 */
84 @MainThread
85 public @NonNull AppStartup addPostRender(@NonNull Runnable task) {
86 postRender.add(new Task("", task));
87 return this;
88 }
89
90 /**
91 * Indicates a UI event critical to initial rendering has started. This will delay tasks that were
92 * scheduled via {@link #addPostRender(Runnable)}. You MUST call
93 * {@link #onCriticalRenderEventEnd()} for each invocation of this method.
94 */
95 @MainThread
96 public void onCriticalRenderEventStart() {
97 if (outstandingCriticalRenderEvents == 0 && postRender.size() > 0) {
98 Log.i(TAG, "Received first critical render event.");
99 renderStartTime = System.currentTimeMillis();
100 SignalLocalMetrics.ColdStart.onRenderStart();
101
102 postRenderHandler.removeCallbacksAndMessages(null);
103 postRenderHandler.postDelayed(() -> {
104 Log.w(TAG, "Reached the failsafe event for post-render! Either someone forgot to call #onRenderEnd(), the activity was started while the phone was locked, or app start is taking a very long time.");
105 executePostRender();
106 }, FAILSAFE_RENDER_TIME);
107 }
108
109 outstandingCriticalRenderEvents++;
110 }
111
112 /**
113 * Indicates a UI event critical to initial rendering has ended. Should only be paired with
114 * {@link #onCriticalRenderEventStart()}.
115 */
116 @MainThread
117 public void onCriticalRenderEventEnd() {
118 if (outstandingCriticalRenderEvents <= 0) {
119 Log.w(TAG, "Too many end events! onCriticalRenderEventStart/End was mismanaged.");
120 }
121
122 outstandingCriticalRenderEvents = Math.max(outstandingCriticalRenderEvents - 1, 0);
123
124 if (outstandingCriticalRenderEvents == 0 && postRender.size() > 0) {
125 renderEndTime = System.currentTimeMillis();
126 SignalLocalMetrics.ColdStart.onRenderFinished();
127
128 Log.i(TAG, "First render has finished. " +
129 "Cold Start: " + (renderEndTime - applicationStartTime) + " ms, " +
130 "Render Time: " + (renderEndTime - renderStartTime) + " ms");
131
132 postRenderHandler.removeCallbacksAndMessages(null);
133 executePostRender();
134 }
135 }
136
137 /**
138 * Begins all pending task execution.
139 */
140 @MainThread
141 public void execute() {
142 Stopwatch stopwatch = new Stopwatch("init");
143
144 for (Task task : blocking) {
145 task.getRunnable().run();
146 stopwatch.split(task.getName());
147 }
148 blocking.clear();
149
150 for (Task task : nonBlocking) {
151 SignalExecutors.BOUNDED.execute(task.getRunnable());
152 }
153 nonBlocking.clear();
154
155 stopwatch.split("schedule-non-blocking");
156 stopwatch.stop(TAG);
157
158 postRenderHandler.postDelayed(() -> {
159 Log.i(TAG, "Assuming the application has started in the background. Running post-render tasks.");
160 executePostRender();
161 }, UI_WAIT_TIME);
162 }
163
164 private void executePostRender() {
165 for (Task task : postRender) {
166 SignalExecutors.BOUNDED.execute(task.getRunnable());
167 }
168 postRender.clear();
169 }
170
171 private class Task {
172 private final String name;
173 private final Runnable runnable;
174
175 protected Task(@NonNull String name, @NonNull Runnable runnable) {
176 this.name = name;
177 this.runnable = runnable;
178 }
179
180 @NonNull String getName() {
181 return name;
182 }
183
184 public @NonNull Runnable getRunnable() {
185 return runnable;
186 }
187 }
188}