• 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.TypedValue;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.Locale;
25 import org.robolectric.RuntimeEnvironment;
26 import org.robolectric.annotation.HiddenApi;
27 import org.robolectric.annotation.Implementation;
28 import org.robolectric.annotation.Implements;
29 import org.robolectric.annotation.RealObject;
30 import org.robolectric.annotation.Resetter;
31 import org.robolectric.res.Plural;
32 import org.robolectric.res.PluralRules;
33 import org.robolectric.res.ResName;
34 import org.robolectric.res.ResType;
35 import org.robolectric.res.ResourceTable;
36 import org.robolectric.res.TypedResource;
37 import org.robolectric.shadows.ShadowResourcesImpl.Picker;
38 import org.robolectric.util.ReflectionHelpers;
39 
40 @SuppressWarnings("NewApi")
41 @Implements(value = ResourcesImpl.class, isInAndroidSdk = false, minSdk = N,
42     shadowPicker = Picker.class)
43 public class ShadowLegacyResourcesImpl extends ShadowResourcesImpl {
44 
45   @Resetter
reset()46   public static void reset() {
47     if (RuntimeEnvironment.useLegacyResources()) {
48       ShadowResourcesImpl.reset();
49     }
50   }
51 
52   @RealObject
53   private ResourcesImpl realResourcesImpl;
54 
55 
56   @Implementation(maxSdk = M)
getQuantityString(int id, int quantity, Object... formatArgs)57   public String getQuantityString(int id, int quantity, Object... formatArgs) throws Resources.NotFoundException {
58     String raw = getQuantityString(id, quantity);
59     return String.format(Locale.ENGLISH, raw, formatArgs);
60   }
61 
62   @Implementation(maxSdk = M)
getQuantityString(int resId, int quantity)63   public String getQuantityString(int resId, int quantity) throws Resources.NotFoundException {
64     ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
65 
66     TypedResource typedResource = shadowAssetManager.getResourceTable().getValue(resId, shadowAssetManager.config);
67     if (typedResource != null && typedResource instanceof PluralRules) {
68       PluralRules pluralRules = (PluralRules) typedResource;
69       Plural plural = pluralRules.find(quantity);
70 
71       if (plural == null) {
72         return null;
73       }
74 
75       TypedResource<?> resolvedTypedResource = shadowAssetManager.resolve(
76           new TypedResource<>(plural.getString(), ResType.CHAR_SEQUENCE, pluralRules.getXmlContext()), shadowAssetManager.config, resId);
77       return resolvedTypedResource == null ? null : resolvedTypedResource.asString();
78     } else {
79       return null;
80     }
81   }
82 
83   @Implementation(maxSdk = M)
openRawResource(int id)84   public InputStream openRawResource(int id) throws Resources.NotFoundException {
85     ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
86     ResourceTable resourceTable = shadowAssetManager.getResourceTable();
87     InputStream inputStream = resourceTable.getRawValue(id, shadowAssetManager.config);
88     if (inputStream == null) {
89       throw newNotFoundException(id);
90     } else {
91       return inputStream;
92     }
93   }
94 
95   /**
96    * Since {@link AssetFileDescriptor}s are not yet supported by Robolectric, {@code null} will
97    * be returned if the resource is found. If the resource cannot be found, {@link Resources.NotFoundException} will
98    * be thrown.
99    */
100   @Implementation(maxSdk = M)
openRawResourceFd(int id)101   public AssetFileDescriptor openRawResourceFd(int id) throws Resources.NotFoundException {
102     InputStream inputStream = openRawResource(id);
103     if (!(inputStream instanceof FileInputStream)) {
104       // todo fixme
105       return null;
106     }
107 
108     FileInputStream fis = (FileInputStream) inputStream;
109     try {
110       return new AssetFileDescriptor(ParcelFileDescriptor.dup(fis.getFD()), 0, fis.getChannel().size());
111     } catch (IOException e) {
112       throw newNotFoundException(id);
113     }
114   }
115 
newNotFoundException(int id)116   private Resources.NotFoundException newNotFoundException(int id) {
117     ResourceTable resourceTable = legacyShadowOf(realResourcesImpl.getAssets()).getResourceTable();
118     ResName resName = resourceTable.getResName(id);
119     if (resName == null) {
120       return new Resources.NotFoundException("resource ID #0x" + Integer.toHexString(id));
121     } else {
122       return new Resources.NotFoundException(resName.getFullyQualifiedName());
123     }
124   }
125 
126   @HiddenApi @Implementation(maxSdk = M)
loadXmlResourceParser(int resId, String type)127   public XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException {
128     ShadowLegacyAssetManager shadowAssetManager = legacyShadowOf(realResourcesImpl.getAssets());
129     return shadowAssetManager.loadXmlResourceParser(resId, type);
130   }
131 
132   @HiddenApi @Implementation
loadXmlResourceParser(String file, int id, int assetCookie, String type)133   public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws Resources.NotFoundException {
134     return loadXmlResourceParser(id, type);
135   }
136 
137   @Implements(value = ResourcesImpl.ThemeImpl.class, minSdk = N, isInAndroidSdk = false,
138       shadowPicker = ShadowResourcesImpl.ShadowThemeImpl.Picker.class)
139   public static class ShadowLegacyThemeImpl extends ShadowThemeImpl {
140     @RealObject ResourcesImpl.ThemeImpl realThemeImpl;
141 
142     @Implementation
obtainStyledAttributes(Resources.Theme wrapper, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)143     public TypedArray obtainStyledAttributes(Resources.Theme wrapper, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
144       Resources resources = wrapper.getResources();
145       AssetManager assets = resources.getAssets();
146       return legacyShadowOf(assets)
147           .attrsToTypedArray(resources, set, attrs, defStyleAttr, getNativePtr(), defStyleRes);
148     }
149 
getNativePtr()150     public long getNativePtr() {
151       return ReflectionHelpers.getField(realThemeImpl, "mTheme");
152     }
153   }
154 
155   @Implementation(maxSdk = N_MR1)
loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache)156   public Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, boolean useCache) throws Resources.NotFoundException {
157     Drawable drawable = directlyOn(realResourcesImpl, ResourcesImpl.class, "loadDrawable",
158         from(Resources.class, wrapper),
159         from(TypedValue.class, value),
160         from(int.class, id),
161         from(Resources.Theme.class, theme),
162         from(boolean.class, useCache)
163     );
164 
165     ShadowResources.setCreatedFromResId(wrapper, id, drawable);
166     return drawable;
167   }
168 
169   @Implementation(minSdk = O)
loadDrawable(Resources wrapper, TypedValue value, int id, int density, Resources.Theme theme)170   public Drawable loadDrawable(Resources wrapper,  TypedValue value, int id, int density, Resources.Theme theme) {
171     Drawable drawable = directlyOn(realResourcesImpl, ResourcesImpl.class, "loadDrawable",
172         from(Resources.class, wrapper),
173         from(TypedValue.class, value),
174         from(int.class, id),
175         from(int.class, density),
176         from(Resources.Theme.class, theme));
177 
178     ShadowResources.setCreatedFromResId(wrapper, id, drawable);
179     return drawable;
180   }
181 }
182