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