That fuck shit the fascists are using
1package org.tm.archive.service;
2
3import android.app.AlarmManager;
4import android.app.Application;
5import android.app.PendingIntent;
6import android.content.BroadcastReceiver;
7import android.content.Context;
8import android.content.Intent;
9import android.os.Build;
10import android.os.Handler;
11import android.os.HandlerThread;
12
13import androidx.annotation.AnyThread;
14import androidx.annotation.NonNull;
15import androidx.annotation.Nullable;
16import androidx.annotation.WorkerThread;
17
18import org.signal.core.util.PendingIntentFlags;
19import org.signal.core.util.ThreadUtil;
20import org.signal.core.util.logging.Log;
21import org.tm.archive.util.ServiceUtil;
22
23/**
24 * Class to help manage scheduling events to happen in the future, whether the app is open or not.
25 */
26public abstract class TimedEventManager<E> {
27
28 private static final String TAG = Log.tag(TimedEventManager.class);
29
30 private final Application application;
31 private final Handler handler;
32
33 public TimedEventManager(@NonNull Application application, @NonNull String threadName) {
34 HandlerThread handlerThread = new HandlerThread(threadName, ThreadUtil.PRIORITY_BACKGROUND_THREAD);
35 handlerThread.start();
36
37 this.application = application;
38 this.handler = new Handler(handlerThread.getLooper());
39 }
40
41 /**
42 * Should be called whenever the underlying data of events has changed. Will appropriately
43 * schedule new event executions.
44 */
45 public void scheduleIfNecessary() {
46 handler.removeCallbacksAndMessages(null);
47
48 handler.post(() -> {
49 E event = getNextClosestEvent();
50
51 if (event != null) {
52 long delay = getDelayForEvent(event);
53
54 handler.postDelayed(() -> {
55 executeEvent(event);
56 scheduleIfNecessary();
57 }, delay);
58
59 scheduleAlarm(application, event, delay);
60 }
61 });
62 }
63
64 /**
65 * @return The next event that should be executed, or {@code null} if there are no events to execute.
66 */
67 @WorkerThread
68 protected @Nullable abstract E getNextClosestEvent();
69
70 /**
71 * Execute the provided event.
72 */
73 @WorkerThread
74 protected abstract void executeEvent(@NonNull E event);
75
76 /**
77 * @return How long before the provided event should be executed.
78 */
79 @WorkerThread
80 protected abstract long getDelayForEvent(@NonNull E event);
81
82 /**
83 * Schedules an alarm to call {@link #scheduleIfNecessary()} after the specified delay. You can
84 * use {@link #setAlarm(Context, long, Class)} as a helper method.
85 */
86 @AnyThread
87 protected abstract void scheduleAlarm(@NonNull Application application, E event, long delay);
88
89 /**
90 * Helper method to set an alarm.
91 */
92 protected static void setAlarm(@NonNull Context context, long delay, @NonNull Class<? extends BroadcastReceiver> alarmClass) {
93 Intent intent = new Intent(context, alarmClass);
94 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable());
95 AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
96
97 alarmManager.cancel(pendingIntent);
98 alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, pendingIntent);
99 }
100
101 protected static void trySetExactAlarm(@NonNull Context context, long timestamp, @NonNull Class<? extends BroadcastReceiver> alarmClass, @NonNull PendingIntent showIntent) {
102 Intent intent = new Intent(context, alarmClass);
103 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable());
104 AlarmManager alarmManager = ServiceUtil.getAlarmManager(context);
105
106 alarmManager.cancel(pendingIntent);
107
108 boolean hasManagerPermission = Build.VERSION.SDK_INT < 31 || alarmManager.canScheduleExactAlarms();
109 if (hasManagerPermission) {
110 try {
111 alarmManager.setAlarmClock(new AlarmManager.AlarmClockInfo(timestamp, showIntent), pendingIntent);
112 return;
113 } catch (Exception e) {
114 Log.w(TAG, e);
115 }
116 }
117
118 Log.w(TAG, "Unable to schedule exact alarm, falling back to inexact alarm, scheduling alarm for: " + timestamp);
119 alarmManager.set(AlarmManager.RTC_WAKEUP, timestamp, pendingIntent);
120 }
121
122 protected static void cancelAlarm(@NonNull Context context, @NonNull Class<? extends BroadcastReceiver> alarmClass) {
123 Intent intent = new Intent(context, alarmClass);
124 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.mutable());
125
126 try {
127 pendingIntent.cancel();
128 ServiceUtil.getAlarmManager(context).cancel(pendingIntent);
129 } catch (Exception e) {
130 Throwable cause = e;
131 int depth = 0;
132 while (cause != null && depth < 5) {
133 if (cause instanceof SecurityException) {
134 break;
135 } else {
136 cause = e.getCause();
137 depth++;
138 }
139 }
140
141 if (e instanceof SecurityException) {
142 Log.i(TAG, "Unable to cancel alarm because we don't have permission");
143 } else {
144 throw e;
145 }
146 }
147 }
148}