1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package androidx.appcompat.app; 18 19 import android.content.res.Resources; 20 import android.os.Build; 21 import android.util.Log; 22 import android.util.LongSparseArray; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.RequiresApi; 26 27 import java.lang.reflect.Field; 28 import java.util.Map; 29 30 class ResourcesFlusher { 31 private static final String TAG = "ResourcesFlusher"; 32 33 private static Field sDrawableCacheField; 34 private static boolean sDrawableCacheFieldFetched; 35 36 private static Class sThemedResourceCacheClazz; 37 private static boolean sThemedResourceCacheClazzFetched; 38 39 private static Field sThemedResourceCache_mUnthemedEntriesField; 40 private static boolean sThemedResourceCache_mUnthemedEntriesFieldFetched; 41 42 private static Field sResourcesImplField; 43 private static boolean sResourcesImplFieldFetched; 44 45 static boolean flush(@NonNull final Resources resources) { 46 if (Build.VERSION.SDK_INT >= 24) { 47 return flushNougats(resources); 48 } else if (Build.VERSION.SDK_INT >= 23) { 49 return flushMarshmallows(resources); 50 } else if (Build.VERSION.SDK_INT >= 21) { 51 return flushLollipops(resources); 52 } 53 return false; 54 } 55 56 @RequiresApi(21) 57 private static boolean flushLollipops(@NonNull final Resources resources) { 58 if (!sDrawableCacheFieldFetched) { 59 try { 60 sDrawableCacheField = Resources.class.getDeclaredField("mDrawableCache"); 61 sDrawableCacheField.setAccessible(true); 62 } catch (NoSuchFieldException e) { 63 Log.e(TAG, "Could not retrieve Resources#mDrawableCache field", e); 64 } 65 sDrawableCacheFieldFetched = true; 66 } 67 if (sDrawableCacheField != null) { 68 Map drawableCache = null; 69 try { 70 drawableCache = (Map) sDrawableCacheField.get(resources); 71 } catch (IllegalAccessException e) { 72 Log.e(TAG, "Could not retrieve value from Resources#mDrawableCache", e); 73 } 74 if (drawableCache != null) { 75 drawableCache.clear(); 76 return true; 77 } 78 } 79 return false; 80 } 81 82 @RequiresApi(23) 83 private static boolean flushMarshmallows(@NonNull final Resources resources) { 84 if (!sDrawableCacheFieldFetched) { 85 try { 86 sDrawableCacheField = Resources.class.getDeclaredField("mDrawableCache"); 87 sDrawableCacheField.setAccessible(true); 88 } catch (NoSuchFieldException e) { 89 Log.e(TAG, "Could not retrieve Resources#mDrawableCache field", e); 90 } 91 sDrawableCacheFieldFetched = true; 92 } 93 94 Object drawableCache = null; 95 if (sDrawableCacheField != null) { 96 try { 97 drawableCache = sDrawableCacheField.get(resources); 98 } catch (IllegalAccessException e) { 99 Log.e(TAG, "Could not retrieve value from Resources#mDrawableCache", e); 100 } 101 } 102 103 if (drawableCache == null) { 104 // If there is no drawable cache, there's nothing to flush... 105 return false; 106 } 107 108 return drawableCache != null && flushThemedResourcesCache(drawableCache); 109 } 110 111 @RequiresApi(24) 112 private static boolean flushNougats(@NonNull final Resources resources) { 113 if (!sResourcesImplFieldFetched) { 114 try { 115 sResourcesImplField = Resources.class.getDeclaredField("mResourcesImpl"); 116 sResourcesImplField.setAccessible(true); 117 } catch (NoSuchFieldException e) { 118 Log.e(TAG, "Could not retrieve Resources#mResourcesImpl field", e); 119 } 120 sResourcesImplFieldFetched = true; 121 } 122 123 if (sResourcesImplField == null) { 124 // If the mResourcesImpl field isn't available, bail out now 125 return false; 126 } 127 128 Object resourcesImpl = null; 129 try { 130 resourcesImpl = sResourcesImplField.get(resources); 131 } catch (IllegalAccessException e) { 132 Log.e(TAG, "Could not retrieve value from Resources#mResourcesImpl", e); 133 } 134 135 if (resourcesImpl == null) { 136 // If there is no impl instance, bail out now 137 return false; 138 } 139 140 if (!sDrawableCacheFieldFetched) { 141 try { 142 sDrawableCacheField = resourcesImpl.getClass().getDeclaredField("mDrawableCache"); 143 sDrawableCacheField.setAccessible(true); 144 } catch (NoSuchFieldException e) { 145 Log.e(TAG, "Could not retrieve ResourcesImpl#mDrawableCache field", e); 146 } 147 sDrawableCacheFieldFetched = true; 148 } 149 150 Object drawableCache = null; 151 if (sDrawableCacheField != null) { 152 try { 153 drawableCache = sDrawableCacheField.get(resourcesImpl); 154 } catch (IllegalAccessException e) { 155 Log.e(TAG, "Could not retrieve value from ResourcesImpl#mDrawableCache", e); 156 } 157 } 158 159 return drawableCache != null && flushThemedResourcesCache(drawableCache); 160 } 161 162 @RequiresApi(16) 163 private static boolean flushThemedResourcesCache(@NonNull final Object cache) { 164 if (!sThemedResourceCacheClazzFetched) { 165 try { 166 sThemedResourceCacheClazz = Class.forName("android.content.res.ThemedResourceCache"); 167 } catch (ClassNotFoundException e) { 168 Log.e(TAG, "Could not find ThemedResourceCache class", e); 169 } 170 sThemedResourceCacheClazzFetched = true; 171 } 172 173 if (sThemedResourceCacheClazz == null) { 174 // If the ThemedResourceCache class isn't available, bail out now 175 return false; 176 } 177 178 if (!sThemedResourceCache_mUnthemedEntriesFieldFetched) { 179 try { 180 sThemedResourceCache_mUnthemedEntriesField = 181 sThemedResourceCacheClazz.getDeclaredField("mUnthemedEntries"); 182 sThemedResourceCache_mUnthemedEntriesField.setAccessible(true); 183 } catch (NoSuchFieldException ee) { 184 Log.e(TAG, "Could not retrieve ThemedResourceCache#mUnthemedEntries field", ee); 185 } 186 sThemedResourceCache_mUnthemedEntriesFieldFetched = true; 187 } 188 189 if (sThemedResourceCache_mUnthemedEntriesField == null) { 190 // Didn't get mUnthemedEntries field, bail out... 191 return false; 192 } 193 194 LongSparseArray unthemedEntries = null; 195 try { 196 unthemedEntries = (LongSparseArray) 197 sThemedResourceCache_mUnthemedEntriesField.get(cache); 198 } catch (IllegalAccessException e) { 199 Log.e(TAG, "Could not retrieve value from ThemedResourceCache#mUnthemedEntries", e); 200 } 201 202 if (unthemedEntries != null) { 203 unthemedEntries.clear(); 204 return true; 205 } 206 return false; 207 } 208 209 private ResourcesFlusher() { 210 } 211 } 212