• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.JELLY_BEAN;
4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
5 import static android.os.Build.VERSION_CODES.LOLLIPOP;
6 import static android.os.Build.VERSION_CODES.M;
7 import static android.os.Build.VERSION_CODES.P;
8 
9 import android.content.ContentResolver;
10 import android.content.Context;
11 import android.os.Build;
12 import android.provider.Settings;
13 import android.text.TextUtils;
14 import java.util.Arrays;
15 import java.util.HashMap;
16 import java.util.HashSet;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.WeakHashMap;
20 import org.robolectric.RuntimeEnvironment;
21 import org.robolectric.annotation.Implementation;
22 import org.robolectric.annotation.Implements;
23 import org.robolectric.annotation.Resetter;
24 import org.robolectric.shadow.api.Shadow;
25 import org.robolectric.util.ReflectionHelpers.ClassParameter;
26 
27 @SuppressWarnings({"UnusedDeclaration"})
28 @Implements(Settings.class)
29 public class ShadowSettings {
30   @Implements(value = Settings.System.class)
31   public static class ShadowSystem {
32     private static final Map<ContentResolver, Map<String, String>> dataMap = new WeakHashMap<>();
33 
34     @Resetter
reset()35     public static void reset() {
36       dataMap.clear();
37     }
38 
39     @Implementation(minSdk = JELLY_BEAN_MR1)
putStringForUser(ContentResolver cr, String name, String value, int userHandle)40     protected static boolean putStringForUser(ContentResolver cr, String name, String value,
41         int userHandle) {
42       return putString(cr, name, value);
43     }
44 
45     @Implementation(minSdk = JELLY_BEAN_MR1)
getStringForUser(ContentResolver cr, String name, int userHandle)46     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
47       return getString(cr, name);
48     }
49 
50     @Implementation
putString(ContentResolver cr, String name, String value)51     protected static boolean putString(ContentResolver cr, String name, String value) {
52       get(cr).put(name, value);
53       return true;
54     }
55 
56     @Implementation
getString(ContentResolver cr, String name)57     protected static String getString(ContentResolver cr, String name) {
58       return get(cr).get(name);
59     }
60 
get(ContentResolver cr)61     private static Map<String, String> get(ContentResolver cr) {
62       Map<String, String> map = dataMap.get(cr);
63       if (map == null) {
64         map = new HashMap<>();
65         dataMap.put(cr, map);
66       }
67       return map;
68     }
69   }
70 
71   @Implements(value = Settings.Secure.class)
72   public static class ShadowSecure {
73     private static final Map<ContentResolver, Map<String, String>> dataMap = new WeakHashMap<>();
74 
75     @Resetter
reset()76     public static void reset() {
77       dataMap.clear();
78     }
79 
80     @Implementation(minSdk = JELLY_BEAN_MR1)
putStringForUser(ContentResolver cr, String name, String value, int userHandle)81     protected static boolean putStringForUser(ContentResolver cr, String name, String value,
82         int userHandle) {
83       return putString(cr, name, value);
84     }
85 
86     @Implementation(minSdk = JELLY_BEAN_MR1)
getStringForUser(ContentResolver cr, String name, int userHandle)87     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
88       return getString(cr, name);
89     }
90 
91     @Implementation
putString(ContentResolver cr, String name, String value)92     protected static boolean putString(ContentResolver cr, String name, String value) {
93       get(cr).put(name, value);
94       return true;
95     }
96 
97     @Implementation
getString(ContentResolver cr, String name)98     protected static String getString(ContentResolver cr, String name) {
99       return get(cr).get(name);
100     }
101 
get(ContentResolver cr)102     private static Map<String, String> get(ContentResolver cr) {
103       Map<String, String> map = dataMap.get(cr);
104       if (map == null) {
105         map = new HashMap<>();
106         dataMap.put(cr, map);
107       }
108       return map;
109     }
110 
111     @Implementation(minSdk = JELLY_BEAN_MR1)
setLocationProviderEnabledForUser( ContentResolver cr, String provider, boolean enabled, int uid)112     protected static boolean setLocationProviderEnabledForUser(
113         ContentResolver cr, String provider, boolean enabled, int uid) {
114       return updateEnabledProviders(cr, provider, enabled);
115     }
116 
117     @Implementation(maxSdk = JELLY_BEAN)
setLocationProviderEnabled( ContentResolver cr, String provider, boolean enabled)118     protected static void setLocationProviderEnabled(
119         ContentResolver cr, String provider, boolean enabled) {
120       updateEnabledProviders(cr, provider, enabled);
121     }
122 
updateEnabledProviders( ContentResolver cr, String provider, boolean enabled)123     private static boolean updateEnabledProviders(
124         ContentResolver cr, String provider, boolean enabled) {
125       Set<String> providers = new HashSet<>();
126       String oldProviders =
127           Settings.Secure.getString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
128       if (!TextUtils.isEmpty(oldProviders)) {
129         providers.addAll(Arrays.asList(oldProviders.split(",")));
130       }
131 
132       if (enabled) {
133         providers.add(provider);
134       } else {
135         providers.remove(provider);
136       }
137 
138       String newProviders = TextUtils.join(",", providers.toArray());
139       return Settings.Secure.putString(
140           cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders);
141     }
142 
143     @Implementation
putInt(ContentResolver resolver, String name, int value)144     protected static boolean putInt(ContentResolver resolver, String name, int value) {
145       if (Settings.Secure.LOCATION_MODE.equals(name)
146           && RuntimeEnvironment.getApiLevel() >= LOLLIPOP
147           && RuntimeEnvironment.getApiLevel() <= P) {
148         // Map LOCATION_MODE to underlying location provider storage API
149         return Shadow.directlyOn(
150             Settings.Secure.class,
151             "setLocationModeForUser",
152             ClassParameter.from(ContentResolver.class, resolver),
153             ClassParameter.from(int.class, value),
154             ClassParameter.from(int.class, 0));
155       }
156       return Shadow.directlyOn(
157           Settings.Secure.class,
158           "putInt",
159           ClassParameter.from(ContentResolver.class, resolver),
160           ClassParameter.from(String.class, name),
161           ClassParameter.from(int.class, value));
162     }
163 
164     @Implementation
getInt(ContentResolver resolver, String name)165     protected static int getInt(ContentResolver resolver, String name) {
166       if (Settings.Secure.LOCATION_MODE.equals(name)
167           && RuntimeEnvironment.getApiLevel() >= LOLLIPOP
168           && RuntimeEnvironment.getApiLevel() <= P) {
169         // Map from to underlying location provider storage API to location mode
170         return Shadow.directlyOn(
171             Settings.Secure.class,
172             "getLocationModeForUser",
173             ClassParameter.from(ContentResolver.class, resolver),
174             ClassParameter.from(int.class, 0));
175       }
176 
177       return Shadow.directlyOn(
178           Settings.Secure.class,
179           "getInt",
180           ClassParameter.from(ContentResolver.class, resolver),
181           ClassParameter.from(String.class, name));
182     }
183 
184     @Implementation
getInt(ContentResolver resolver, String name, int def)185     protected static int getInt(ContentResolver resolver, String name, int def) {
186       if (Settings.Secure.LOCATION_MODE.equals(name)
187           && RuntimeEnvironment.getApiLevel() >= LOLLIPOP
188           && RuntimeEnvironment.getApiLevel() <= P) {
189         // Map from to underlying location provider storage API to location mode
190         return Shadow.directlyOn(
191             Settings.Secure.class,
192             "getLocationModeForUser",
193             ClassParameter.from(ContentResolver.class, resolver),
194             ClassParameter.from(int.class, 0));
195       }
196 
197       return Shadow.directlyOn(
198           Settings.Secure.class,
199           "getInt",
200           ClassParameter.from(ContentResolver.class, resolver),
201           ClassParameter.from(String.class, name),
202           ClassParameter.from(int.class, def));
203     }
204   }
205 
206   @Implements(value = Settings.Global.class, minSdk = JELLY_BEAN_MR1)
207   public static class ShadowGlobal {
208     private static final Map<ContentResolver, Map<String, String>> dataMap = new WeakHashMap<>();
209 
210     @Resetter
reset()211     public static void reset() {
212       dataMap.clear();
213     }
214 
215     @Implementation(minSdk = JELLY_BEAN_MR1)
putStringForUser(ContentResolver cr, String name, String value, int userHandle)216     protected static boolean putStringForUser(ContentResolver cr, String name, String value,
217         int userHandle) {
218       return putString(cr, name, value);
219     }
220 
221     @Implementation(minSdk = JELLY_BEAN_MR1)
getStringForUser(ContentResolver cr, String name, int userHandle)222     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
223       return getString(cr, name);
224     }
225 
226     @Implementation
putString(ContentResolver cr, String name, String value)227     protected static boolean putString(ContentResolver cr, String name, String value) {
228       get(cr).put(name, value);
229       return true;
230     }
231 
232     @Implementation
getString(ContentResolver cr, String name)233     protected static String getString(ContentResolver cr, String name) {
234       return get(cr).get(name);
235     }
236 
get(ContentResolver cr)237     private static Map<String, String> get(ContentResolver cr) {
238       Map<String, String> map = dataMap.get(cr);
239       if (map == null) {
240         map = new HashMap<>();
241         dataMap.put(cr, map);
242       }
243       return map;
244     }
245   }
246 
247   // BEGIN-INTERNAL
248   @Implements(value = Settings.Config.class, minSdk = Build.VERSION_CODES.Q)
249   public static class ShadowConfig {
250     private static final Map<ContentResolver, Map<String, String>> dataMap = new WeakHashMap<>();
251 
252     @Resetter
reset()253     public static void reset() {
254       dataMap.clear();
255     }
256 
257     @Implementation
putString(ContentResolver cr, String name, String value)258     protected static boolean putString(ContentResolver cr, String name, String value) {
259       get(cr).put(name, value);
260       return true;
261     }
262 
263     @Implementation
getString(ContentResolver cr, String name)264     protected static String getString(ContentResolver cr, String name) {
265       return get(cr).get(name);
266     }
267 
get(ContentResolver cr)268     private static Map<String, String> get(ContentResolver cr) {
269       Map<String, String> map = dataMap.get(cr);
270       if (map == null) {
271         map = new HashMap<>();
272         dataMap.put(cr, map);
273       }
274       return map;
275     }
276   }
277   // END-INTERNAL
278 
279   /**
280    * Sets the value of the {@link Settings.System#AIRPLANE_MODE_ON} setting.
281    *
282    * @param isAirplaneMode new status for airplane mode
283    */
setAirplaneMode(boolean isAirplaneMode)284   public static void setAirplaneMode(boolean isAirplaneMode) {
285     Settings.Global.putInt(
286         RuntimeEnvironment.application.getContentResolver(),
287         Settings.Global.AIRPLANE_MODE_ON,
288         isAirplaneMode ? 1 : 0);
289     Settings.System.putInt(
290         RuntimeEnvironment.application.getContentResolver(),
291         Settings.System.AIRPLANE_MODE_ON,
292         isAirplaneMode ? 1 : 0);
293   }
294 
295   /**
296    * Non-Android accessor that allows the value of the WIFI_ON setting to be set.
297    *
298    * @param isOn new status for wifi mode
299    */
setWifiOn(boolean isOn)300   public static void setWifiOn(boolean isOn) {
301     Settings.Global.putInt(
302         RuntimeEnvironment.application.getContentResolver(), Settings.Global.WIFI_ON, isOn ? 1 : 0);
303     Settings.System.putInt(
304         RuntimeEnvironment.application.getContentResolver(), Settings.System.WIFI_ON, isOn ? 1 : 0);
305   }
306 
307   /**
308    * Sets the value of the {@link Settings.System#TIME_12_24} setting.
309    *
310    * @param use24HourTimeFormat new status for the time setting
311    */
set24HourTimeFormat(boolean use24HourTimeFormat)312   public static void set24HourTimeFormat(boolean use24HourTimeFormat) {
313     Settings.System.putString(RuntimeEnvironment.application.getContentResolver(), Settings.System.TIME_12_24, use24HourTimeFormat ? "24" : "12");
314   }
315 
316   private static boolean canDrawOverlays = false;
317 
318   /** @return `false` by default, or the value specified via {@link #setCanDrawOverlays(boolean)} */
319   @Implementation(minSdk = M)
canDrawOverlays(Context context)320   protected static boolean canDrawOverlays(Context context) {
321     return canDrawOverlays;
322   }
323 
324   /** Sets the value returned by {@link #canDrawOverlays(Context)}. */
setCanDrawOverlays(boolean canDrawOverlays)325   public static void setCanDrawOverlays(boolean canDrawOverlays) {
326     ShadowSettings.canDrawOverlays = canDrawOverlays;
327   }
328 
329   /**
330    * Sets the value of the {@link Settings.Global#ADB_ENABLED} setting or {@link
331    * Settings.Secure#ADB_ENABLED} depending on API level.
332    *
333    * @param adbEnabled new value for whether adb is enabled
334    */
setAdbEnabled(boolean adbEnabled)335   public static void setAdbEnabled(boolean adbEnabled) {
336     // This setting moved from Secure to Global in JELLY_BEAN_MR1
337     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
338       Settings.Global.putInt(
339           RuntimeEnvironment.application.getContentResolver(),
340           Settings.Global.ADB_ENABLED,
341           adbEnabled ? 1 : 0);
342     }
343     // Support all clients by always setting the Secure version of the setting
344     Settings.Secure.putInt(
345         RuntimeEnvironment.application.getContentResolver(),
346         Settings.Secure.ADB_ENABLED,
347         adbEnabled ? 1 : 0);
348   }
349 
350   /**
351    * Sets the value of the {@link Settings.Global#INSTALL_NON_MARKET_APPS} setting or {@link
352    * Settings.Secure#INSTALL_NON_MARKET_APPS} depending on API level.
353    *
354    * @param installNonMarketApps new value for whether non-market apps are allowed to be installed
355    */
setInstallNonMarketApps(boolean installNonMarketApps)356   public static void setInstallNonMarketApps(boolean installNonMarketApps) {
357     // This setting moved from Secure to Global in JELLY_BEAN_MR1 and then moved it back to Global
358     // in LOLLIPOP. Support all clients by always setting this field on all versions >=
359     // JELLY_BEAN_MR1.
360     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
361       Settings.Global.putInt(
362           RuntimeEnvironment.application.getContentResolver(),
363           Settings.Global.INSTALL_NON_MARKET_APPS,
364           installNonMarketApps ? 1 : 0);
365     }
366     // Always set the Secure version of the setting
367     Settings.Secure.putInt(
368         RuntimeEnvironment.application.getContentResolver(),
369         Settings.Secure.INSTALL_NON_MARKET_APPS,
370         installNonMarketApps ? 1 : 0);
371   }
372 
373   @Resetter
reset()374   public static void reset() {
375     canDrawOverlays = false;
376   }
377 }
378