Home | History | Annotate | Download | only in app
      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