• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.settings.testutils.shadow;
2 
3 import static android.util.TypedValue.TYPE_REFERENCE;
4 
5 import static org.robolectric.RuntimeEnvironment.application;
6 import static org.robolectric.Shadows.shadowOf;
7 import static org.robolectric.internal.Shadow.directlyOn;
8 
9 import android.annotation.DimenRes;
10 import android.content.res.ColorStateList;
11 import android.content.res.Resources;
12 import android.content.res.Resources.NotFoundException;
13 import android.content.res.Resources.Theme;
14 import android.content.res.TypedArray;
15 import android.graphics.Color;
16 import android.graphics.drawable.ColorDrawable;
17 import android.graphics.drawable.Drawable;
18 import android.support.annotation.ArrayRes;
19 import android.support.annotation.ColorRes;
20 import android.support.annotation.Nullable;
21 import android.util.AttributeSet;
22 import android.util.SparseArray;
23 import android.util.TypedValue;
24 
25 import com.android.settings.R;
26 
27 import org.robolectric.RuntimeEnvironment;
28 import org.robolectric.annotation.Implementation;
29 import org.robolectric.annotation.Implements;
30 import org.robolectric.annotation.RealObject;
31 import org.robolectric.internal.Shadow;
32 import org.robolectric.res.StyleData;
33 import org.robolectric.res.StyleResolver;
34 import org.robolectric.res.builder.XmlResourceParserImpl;
35 import org.robolectric.shadows.ShadowAssetManager;
36 import org.robolectric.shadows.ShadowResources;
37 import org.robolectric.util.ReflectionHelpers;
38 import org.robolectric.util.ReflectionHelpers.ClassParameter;
39 import org.w3c.dom.Node;
40 
41 import java.util.List;
42 import java.util.Map;
43 
44 /**
45  * Shadow Resources and Theme classes to handle resource references that Robolectric shadows cannot
46  * handle because they are too new or private.
47  */
48 @Implements(Resources.class)
49 public class SettingsShadowResources extends ShadowResources {
50 
51     @RealObject
52     public Resources realResources;
53 
54     private static SparseArray<Object> sResourceOverrides = new SparseArray<>();
55 
overrideResource(int id, Object value)56     public static void overrideResource(int id, Object value) {
57         sResourceOverrides.put(id, value);
58     }
59 
overrideResource(String name, Object value)60     public static void overrideResource(String name, Object value) {
61         final Resources res = application.getResources();
62         final int resId = res.getIdentifier(name, null, null);
63         if (resId == 0) {
64             throw new Resources.NotFoundException("Cannot override \"" + name + "\"");
65         }
66         overrideResource(resId, value);
67     }
68 
reset()69     public static void reset() {
70         sResourceOverrides.clear();
71     }
72 
73     @Implementation
getDimensionPixelSize(@imenRes int id)74     public int getDimensionPixelSize(@DimenRes int id) throws NotFoundException {
75         // Handle requests for private dimension resources,
76         // TODO: Consider making a set of private dimension resource ids if this happens repeatedly.
77         if (id == com.android.internal.R.dimen.preference_fragment_padding_bottom) {
78             return 0;
79         }
80         return directlyOn(realResources, Resources.class).getDimensionPixelSize(id);
81     }
82 
83     @Implementation
getColor(@olorRes int id, @Nullable Theme theme)84     public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
85         if (id == R.color.battery_icon_color_error) {
86             return Color.WHITE;
87         }
88         return directlyOn(realResources, Resources.class).getColor(id, theme);
89     }
90 
91     @Implementation
getColorStateList(@olorRes int id, @Nullable Theme theme)92     public ColorStateList getColorStateList(@ColorRes int id, @Nullable Theme theme)
93             throws NotFoundException {
94         if (id == com.android.internal.R.color.text_color_primary) {
95             return ColorStateList.valueOf(Color.WHITE);
96         }
97         return directlyOn(realResources, Resources.class).getColorStateList(id, theme);
98     }
99 
100     @Implementation
loadDrawable(TypedValue value, int id, Theme theme)101     public Drawable loadDrawable(TypedValue value, int id, Theme theme)
102             throws NotFoundException {
103         // The drawable item in switchbar_background.xml refers to a very recent color attribute
104         // that Robolectric isn't yet aware of.
105         // TODO: Remove this once Robolectric is updated.
106         if (id == R.drawable.switchbar_background) {
107             return new ColorDrawable();
108         } else if (id == R.drawable.ic_launcher_settings) {
109             // ic_launcher_settings uses adaptive-icon, which is not supported by robolectric,
110             // change it to a normal drawable.
111             id = R.drawable.ic_settings_wireless;
112         } else if (id == R.drawable.app_filter_spinner_background) {
113             id = R.drawable.ic_expand_more_inverse;
114         } else if (id == R.drawable.selectable_card_grey) {
115             id = R.drawable.ic_expand_more_inverse;
116         }
117         return super.loadDrawable(value, id, theme);
118     }
119 
120     @Implementation
getIntArray(@rrayRes int id)121     public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
122         // The Robolectric isn't aware of resources in settingslib, so we need to stub it here
123         if (id == com.android.settings.R.array.batterymeter_bolt_points
124                 || id == com.android.settings.R.array.batterymeter_plus_points) {
125             return new int[2];
126         }
127         return directlyOn(realResources, Resources.class).getIntArray(id);
128     }
129 
130     @Implementation
getString(int id)131     public String getString(int id) {
132         final Object override = sResourceOverrides.get(id);
133         if (override instanceof String) {
134             return (String) override;
135         }
136         return Shadow.directlyOn(
137                 realResources, Resources.class, "getString", ClassParameter.from(int.class, id));
138     }
139 
140     @Implementation
getInteger(int id)141     public int getInteger(int id) {
142         final Object override = sResourceOverrides.get(id);
143         if (override instanceof Integer) {
144             return (Integer) override;
145         }
146         return Shadow.directlyOn(
147                 realResources, Resources.class, "getInteger", ClassParameter.from(int.class, id));
148     }
149 
150     @Implementation
getBoolean(int id)151     public boolean getBoolean(int id) {
152         final Object override = sResourceOverrides.get(id);
153         if (override instanceof Boolean) {
154             return (boolean) override;
155         }
156         return Shadow.directlyOn(realResources, Resources.class, "getBoolean",
157                 ClassParameter.from(int.class, id));
158     }
159 
160     @Implements(Theme.class)
161     public static class SettingsShadowTheme extends ShadowTheme {
162 
163         @RealObject
164         Theme realTheme;
165 
166         @Implementation
obtainStyledAttributes( AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)167         public TypedArray obtainStyledAttributes(
168                 AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
169             // Replace all private string references with a placeholder.
170             if (set != null) {
171                 for (int i = 0; i < set.getAttributeCount(); ++i) {
172                     String attributeValue = set.getAttributeValue(i);
173                     Node node = ReflectionHelpers.callInstanceMethod(
174                             XmlResourceParserImpl.class, set, "getAttributeAt",
175                             ReflectionHelpers.ClassParameter.from(int.class, i));
176                     if (attributeValue.contains("attr/fingerprint_layout_theme")) {
177                         // Workaround for https://github.com/robolectric/robolectric/issues/2641
178                         node.setNodeValue("@style/FingerprintLayoutTheme");
179                     } else if (attributeValue.startsWith("@*android:string")) {
180                         node.setNodeValue("PLACEHOLDER");
181                     }
182                 }
183             }
184 
185             // Track down all styles and remove all inheritance from private styles.
186             ShadowAssetManager assetManager = shadowOf(RuntimeEnvironment.application.getAssets());
187             // The Object's below are actually ShadowAssetManager.OverlayedStyle. We can't use it
188             // here because it's package private.
189             Map<Long, List<Object>> appliedStylesList =
190                     ReflectionHelpers.getField(assetManager, "appliedStyles");
191             for (Long idx : appliedStylesList.keySet()) {
192                 List<Object> appliedStyles = appliedStylesList.get(idx);
193                 for (Object appliedStyle : appliedStyles) {
194                     StyleResolver styleResolver = ReflectionHelpers.getField(appliedStyle, "style");
195                     List<StyleData> styleDatas =
196                             ReflectionHelpers.getField(styleResolver, "styles");
197                     for (StyleData styleData : styleDatas) {
198                         if (styleData.getParent() != null &&
199                                 styleData.getParent().startsWith("@*android:style")) {
200                             ReflectionHelpers.setField(StyleData.class, styleData, "parent", null);
201                         }
202                     }
203                 }
204 
205             }
206             return super.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
207         }
208 
209         @Implementation
resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs)210         public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
211             // The real Resources instance in Robolectric tests somehow fails to find the
212             // preferenceTheme attribute in the layout. Let's do it ourselves.
213             if (getResources().getResourceName(resid)
214                     .equals("com.android.settings:attr/preferenceTheme")) {
215                 int preferenceThemeResId =
216                         getResources().getIdentifier(
217                                 "PreferenceTheme", "style", "com.android.settings");
218                 outValue.type = TYPE_REFERENCE;
219                 outValue.data = preferenceThemeResId;
220                 outValue.resourceId = preferenceThemeResId;
221                 return true;
222             }
223             return directlyOn(realTheme, Theme.class)
224                     .resolveAttribute(resid, outValue, resolveRefs);
225         }
226     }
227 }
228