That fuck shit the fascists are using
1package org.tm.archive.util;
2
3import android.content.Context;
4import android.content.res.Configuration;
5import android.view.LayoutInflater;
6import android.view.View;
7import android.view.ViewGroup;
8
9import androidx.annotation.LayoutRes;
10import androidx.annotation.MainThread;
11import androidx.annotation.NonNull;
12import androidx.annotation.Nullable;
13import androidx.asynclayoutinflater.appcompat.AsyncAppCompatFactory;
14import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
15
16import org.signal.core.util.ThreadUtil;
17import org.signal.core.util.concurrent.SignalExecutors;
18import org.signal.core.util.logging.Log;
19import org.tm.archive.util.concurrent.SerialExecutor;
20
21import java.util.Collections;
22import java.util.HashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.concurrent.Executor;
27
28/**
29 * A class that can be used to pre-cache layouts. Usage flow:
30 *
31 * - At some point before you want to use the views, call {@link #cacheUntilLimit(int, ViewGroup, int)}.
32 * - Later, use {@link #inflate(int, ViewGroup, boolean)}, which will prefer using cached views
33 * before inflating new ones.
34 */
35public class CachedInflater {
36
37 private static final String TAG = Log.tag(CachedInflater.class);
38
39 private final Context context;
40
41 /**
42 * Does *not* work with the application context.
43 */
44 public static CachedInflater from(@NonNull Context context) {
45 return new CachedInflater(context);
46 }
47
48 private CachedInflater(@NonNull Context context) {
49 this.context = context;
50 }
51
52 /**
53 * Identical to {@link LayoutInflater#inflate(int, ViewGroup, boolean)}, but will prioritize
54 * pulling a cached view first.
55 */
56 @MainThread
57 @SuppressWarnings("unchecked")
58 public <V extends View> V inflate(@LayoutRes int layoutRes, @Nullable ViewGroup parent, boolean attachToRoot) {
59 View cached = ViewCache.getInstance().pull(layoutRes, context.getResources().getConfiguration());
60 if (cached != null) {
61 if (parent != null && attachToRoot) {
62 parent.addView(cached);
63 }
64 return (V) cached;
65 } else {
66 return (V) LayoutInflater.from(context).inflate(layoutRes, parent, attachToRoot);
67 }
68 }
69
70 /**
71 * Will inflate as many views as necessary until the cache holds the amount you specify.
72 */
73 @MainThread
74 public void cacheUntilLimit(@LayoutRes int layoutRes, @Nullable ViewGroup parent, int limit) {
75 ViewCache.getInstance().cacheUntilLimit(context, layoutRes, parent, limit);
76 }
77
78 /**
79 * Clears all cached views. This should be done if, for instance, the theme changes.
80 */
81 @MainThread
82 public void clear() {
83 Log.d(TAG, "Clearing view cache.");
84 ViewCache.getInstance().clear();
85 }
86
87 private static class ViewCache {
88
89 private static final ViewCache INSTANCE = new ViewCache();
90 private static final Executor ENQUEUING_EXECUTOR = new SerialExecutor(SignalExecutors.BOUNDED);
91
92 private final Map<Integer, List<View>> cache = new HashMap<>();
93
94 private long lastClearTime;
95 private int nightModeConfiguration;
96 private float fontScale;
97 private int layoutDirection;
98
99 static ViewCache getInstance() {
100 return INSTANCE;
101 }
102
103 @MainThread
104 void cacheUntilLimit(@NonNull Context context, @LayoutRes int layoutRes, @Nullable ViewGroup parent, int limit) {
105 Configuration configuration = context.getResources().getConfiguration();
106 int currentNightModeConfiguration = ConfigurationUtil.getNightModeConfiguration(configuration);
107 float currentFontScale = ConfigurationUtil.getFontScale(configuration);
108 int currentLayoutDirection = configuration.getLayoutDirection();
109
110 if (nightModeConfiguration != currentNightModeConfiguration ||
111 fontScale != currentFontScale ||
112 layoutDirection != currentLayoutDirection)
113 {
114 clear();
115 nightModeConfiguration = currentNightModeConfiguration;
116 fontScale = currentFontScale;
117 layoutDirection = currentLayoutDirection;
118 }
119
120 AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, new AsyncAppCompatFactory());
121
122 int existingCount = Util.getOrDefault(cache, layoutRes, Collections.emptyList()).size();
123 int inflateCount = Math.max(limit - existingCount, 0);
124 long enqueueTime = System.currentTimeMillis();
125
126 // Calling AsyncLayoutInflator#inflate can block the calling thread when there's a large number of requests.
127 // The method is annotated @UiThread, but unnecessarily so.
128 ENQUEUING_EXECUTOR.execute(() -> {
129 if (enqueueTime < lastClearTime) {
130 Log.d(TAG, "Prefetch is no longer valid. Ignoring " + inflateCount + " inflates.");
131 return;
132 }
133
134 AsyncLayoutInflater.OnInflateFinishedListener onInflateFinishedListener = (view, resId, p) -> {
135 ThreadUtil.assertMainThread();
136 if (enqueueTime < lastClearTime) {
137 Log.d(TAG, "Prefetch is no longer valid. Ignoring.");
138 return;
139 }
140
141 List<View> views = cache.get(resId);
142
143 views = views == null ? new LinkedList<>() : views;
144 views.add(view);
145
146 cache.put(resId, views);
147 };
148
149 for (int i = 0; i < inflateCount; i++) {
150 inflater.inflate(layoutRes, parent, onInflateFinishedListener);
151 }
152 });
153 }
154
155 @MainThread
156 @Nullable View pull(@LayoutRes int layoutRes, @NonNull Configuration configuration) {
157 if (this.nightModeConfiguration != ConfigurationUtil.getNightModeConfiguration(configuration) ||
158 this.fontScale != ConfigurationUtil.getFontScale(configuration) ||
159 this.layoutDirection != configuration.getLayoutDirection())
160 {
161 clear();
162 return null;
163 }
164
165 List<View> views = cache.get(layoutRes);
166 return views != null && !views.isEmpty() ? views.remove(0)
167 : null;
168 }
169
170 @MainThread
171 void clear() {
172 lastClearTime = System.currentTimeMillis();
173 cache.clear();
174 }
175 }
176}