That fuck shit the fascists are using
at master 188 lines 5.7 kB view raw
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}