• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.M;
4 import static android.os.Build.VERSION_CODES.N;
5 import static android.os.Build.VERSION_CODES.N_MR1;
6 import static android.os.Build.VERSION_CODES.O;
7 import static org.robolectric.shadow.api.Shadow.directlyOn;
8 import static org.robolectric.shadows.ShadowAssetManager.legacyShadowOf;
9 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
10 
11 import android.content.res.AssetFileDescriptor;
12 import android.content.res.AssetManager;
13 import android.content.res.Resources;
14 import android.content.res.ResourcesImpl;
15 import android.content.res.TypedArray;
16 import android.content.res.XmlResourceParser;
17 import android.graphics.drawable.Drawable;
18 import android.os.ParcelFileDescriptor;
19 import android.util.AttributeSet;
20 import android.util.LongSparseArray;
21 import android.util.TypedValue;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.lang.reflect.Field;
26 import java.lang.reflect.Modifier;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Locale;
30 import org.robolectric.RuntimeEnvironment;
31 import org.robolectric.annotation.HiddenApi;
32 import org.robolectric.annotation.Implementation;
33 import org.robolectric.annotation.Implements;
34 import org.robolectric.annotation.RealObject;
35 import org.robolectric.annotation.Resetter;
36 import org.robolectric.res.Plural;
37 import org.robolectric.res.PluralRules;
38 import org.robolectric.res.ResName;
39 import org.robolectric.res.ResType;
40 import org.robolectric.res.ResourceTable;
41 import org.robolectric.res.TypedResource;
42 import org.robolectric.shadows.ShadowResourcesImpl.Picker;
43 import org.robolectric.util.ReflectionHelpers;
44 import org.robolectric.util.ReflectionHelpers.ClassParameter;
45 
46 @SuppressWarnings("NewApi")
47 @Implements(value = ResourcesImpl.class, isInAndroidSdk = false, minSdk = N,
48     shadowPicker = Picker.class)
49 public class ShadowArscResourcesImpl extends ShadowResourcesImpl {
50   private static List<LongSparseArray<?>> resettableArrays;
51 
52   @RealObject
53   ResourcesImpl realResourcesImpl;
54 
55   @Resetter
reset()56   public static void reset() {
57     if (RuntimeEnvironment.useLegacyResources()) {
58       ShadowResourcesImpl.reset();
59     }
60   }
61 
obtainResettableArrays()62   private static List<LongSparseArray<?>> obtainResettableArrays() {
63     List<LongSparseArray<?>> resettableArrays = new ArrayList<>();
64     Field[] allFields = Resources.class.getDeclaredFields();
65     for (Field field : allFields) {
66       if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(LongSparseArray.class)) {
67         field.setAccessible(true);
68         try {
69           LongSparseArray<?> longSparseArray = (LongSparseArray<?>) field.get(null);
70           if (longSparseArray != null) {
71             resettableArrays.add(longSparseArray);
72           }
73         } catch (IllegalAccessException e) {
74           throw new RuntimeException(e);
75         }
76       }
77     }
78     return resettableArrays;
79   }
80 
81   @Implementation(maxSdk = M)
getQuantityString(int id, int quantity, Object... formatArgs)82   public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException {
83     String raw = getQuantityString(id, quantity);
84     return String.format(Locale.ENGLISH, raw, formatArgs);
85   }
86 
87   @Implementation(maxSdk = M)
getQuantityString(int resId, int quantity)88   public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException {
89     ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
90 
91     TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config);
92     if (typedResource != null && typedResource instanceof PluralRules) {
93       PluralRules pluralRules = (PluralRules) typedResource;
94       Plural plural = pluralRules.find(quantity);
95 
96       if (plural == null) {
97         return null;
98       }
99 
100       TypedResource<?> resolvedTypedResource = shadowAssetManager.resolve(
101           new TypedResource<>(plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), shadowAssetManager.config, resId);
102       return resolvedTypedResource == null ? null : resolvedTypedResource.asString();
103     } else {
104       return null;
105     }
106   }
107 
108   @Implementation(maxSdk = M)
openRawResource(int id)109   public InputStream openRawResource(int id) throws Resources.NotFoundException {
110     if (false) {
111       ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
112       ResourceTable resourceTable = shadowAssetManager.getResourceTable();
113       InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config);
114       if (inputStream == null) {
115         throw newNotFoundException(id);
116       } else {
117         return inputStream;
118       }
119     } else {
120       return directlyOn(realResourcesImpl, ResourcesImpl.class, "openRawResource",
121           from(int.class, id));
122     }
123   }
124 
125   /**
126    * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will
127    * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will
128    * be thrown.
129    */
130   @Implementation(maxSdk = M)
openRawResourceFd(int id)131   public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException {
132     InputStream inputStream = openRawResource(id);
133     if (!(inputStream instanceof FileInputStream)) {
134       // todo fixme
135       return null;
136     }
137 
138     FileInputStream fis = (FileInputStream) inputStream;
139     try {
140       return new AssetFileDescriptor(ParcelFileDescriptor.dup(fis.getFD()), 0, fis.getChannel().size());
141     } catch (IOException e) {
142       throw newNotFoundException(id);
143     }
144   }
145 
newNotFoundException(int id)146   private Resources.NotFoundException newNotFoundException(int id) {
147     ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable();
148     ResName resName = resourceTable.getResName(id);
149     if (resName == null) {
150       return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id));
151     } else {
152       return new Resources.NotFoundException(resName.getFullyQualifiedName());
153     }
154   }
155 
156   @Implementation(maxSdk = N_MR1)
loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache)157   public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws Resources.NotFoundException {
158     Drawable drawable = directlyOn(realResourcesImpl, ResourcesImpl.class, "loadDrawable",
159         from(Resources.class, wrapper),
160         from(TypedValue.class, value),
161         from(int.class, id),
162         from(Resources.Theme.class, theme),
163         from(boolean.class, useCache)
164     );
165 
166     ShadowResources.setCreatedFromResId(wrapper, id, drawable);
167     return drawable;
168   }
169 
170   @Implementation(minSdk = O)
loadDrawable(Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme)171   public Drawable loadDrawable(Resources wrapper,  TypedValue value, int id, int density, Resources.Theme theme) {
172     Drawable drawable = directlyOn(realResourcesImpl, ResourcesImpl.class, "loadDrawable",
173         from(Resources.class, wrapper),
174         from(TypedValue.class, value),
175         from(int.class, id),
176         from(int.class, density),
177         from(Resources.Theme.class, theme));
178 
179     ShadowResources.setCreatedFromResId(wrapper, id, drawable);
180     return drawable;
181   }
182 }
183