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