1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static android.os.Build.VERSION_CODES.P; 5 import static android.os.Build.VERSION_CODES.Q; 6 import static android.os.Build.VERSION_CODES.R; 7 import static android.os.Build.VERSION_CODES.TIRAMISU; 8 import static android.provider.Settings.Secure.LOCATION_MODE_OFF; 9 import static org.robolectric.util.reflector.Reflector.reflector; 10 11 import android.content.ContentResolver; 12 import android.content.Context; 13 import android.content.Intent; 14 import android.location.LocationManager; 15 import android.provider.Settings; 16 import android.provider.Settings.Secure; 17 import android.provider.Settings.SettingNotFoundException; 18 import android.text.TextUtils; 19 import com.google.common.collect.ImmutableMap; 20 import java.util.Arrays; 21 import java.util.HashMap; 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Objects; 26 import java.util.Optional; 27 import java.util.Set; 28 import java.util.concurrent.ConcurrentHashMap; 29 import org.robolectric.RuntimeEnvironment; 30 import org.robolectric.annotation.Implementation; 31 import org.robolectric.annotation.Implements; 32 import org.robolectric.annotation.Resetter; 33 import org.robolectric.util.reflector.ForType; 34 import org.robolectric.util.reflector.Static; 35 import org.robolectric.versioning.AndroidVersions.U; 36 37 @SuppressWarnings({"UnusedDeclaration"}) 38 @Implements(Settings.class) 39 public class ShadowSettings { 40 41 @Implements(value = Settings.System.class) 42 public static class ShadowSystem { 43 private static final ImmutableMap<String, Optional<Object>> DEFAULTS = 44 ImmutableMap.<String, Optional<Object>>builder() 45 .put(Settings.System.ANIMATOR_DURATION_SCALE, Optional.of(1)) 46 .build(); 47 private static final Map<String, Optional<Object>> settings = new ConcurrentHashMap<>(DEFAULTS); 48 49 @Implementation putInt(ContentResolver cr, String name, int value)50 protected static boolean putInt(ContentResolver cr, String name, int value) { 51 return put(cr, name, value); 52 } 53 54 @Implementation getInt(ContentResolver cr, String name, int def)55 protected static int getInt(ContentResolver cr, String name, int def) { 56 return get(Integer.class, name).orElse(def); 57 } 58 59 @Implementation getInt(ContentResolver cr, String name)60 protected static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { 61 return get(Integer.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 62 } 63 64 @Implementation putString(ContentResolver cr, String name, String value)65 protected static boolean putString(ContentResolver cr, String name, String value) { 66 return put(cr, name, value); 67 } 68 69 @Implementation getString(ContentResolver cr, String name)70 protected static String getString(ContentResolver cr, String name) { 71 return get(String.class, name).orElse(null); 72 } 73 74 @Implementation getStringForUser(ContentResolver cr, String name, int userHandle)75 protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { 76 return get(String.class, name).orElse(null); 77 } 78 79 @Implementation putLong(ContentResolver cr, String name, long value)80 protected static boolean putLong(ContentResolver cr, String name, long value) { 81 return put(cr, name, value); 82 } 83 84 @Implementation getLong(ContentResolver cr, String name, long def)85 protected static long getLong(ContentResolver cr, String name, long def) { 86 return get(Long.class, name).orElse(def); 87 } 88 89 @Implementation getLong(ContentResolver cr, String name)90 protected static long getLong(ContentResolver cr, String name) throws SettingNotFoundException { 91 return get(Long.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 92 } 93 94 @Implementation putFloat(ContentResolver cr, String name, float value)95 protected static boolean putFloat(ContentResolver cr, String name, float value) { 96 boolean result = put(cr, name, value); 97 if (Settings.System.WINDOW_ANIMATION_SCALE.equals(name)) { 98 ShadowValueAnimator.setDurationScale(value); 99 } 100 return result; 101 } 102 103 @Implementation getFloat(ContentResolver cr, String name, float def)104 protected static float getFloat(ContentResolver cr, String name, float def) { 105 return get(Float.class, name).orElse(def); 106 } 107 108 @Implementation getFloat(ContentResolver cr, String name)109 protected static float getFloat(ContentResolver cr, String name) 110 throws SettingNotFoundException { 111 return get(Float.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 112 } 113 put(ContentResolver cr, String name, Object value)114 private static boolean put(ContentResolver cr, String name, Object value) { 115 if (!Objects.equals( 116 settings.put(name, Optional.ofNullable(value)), Optional.ofNullable(value))) { 117 if (cr != null) { 118 cr.notifyChange(Settings.System.getUriFor(name), null); 119 } 120 } 121 return true; 122 } 123 get(Class<T> type, String name)124 private static <T> Optional<T> get(Class<T> type, String name) { 125 return settings.getOrDefault(name, Optional.empty()).filter(type::isInstance).map(type::cast); 126 } 127 128 @Resetter reset()129 public static void reset() { 130 settings.clear(); 131 settings.putAll(DEFAULTS); 132 } 133 } 134 135 @Implements(value = Settings.Secure.class) 136 public static class ShadowSecure { 137 private static final HashMap<String, Optional<Object>> SECURE_DEFAULTS = new HashMap<>(); 138 139 // source of truth for initial location state 140 static final boolean INITIAL_GPS_PROVIDER_STATE = true; 141 static final boolean INITIAL_NETWORK_PROVIDER_STATE = false; 142 143 static { 144 if (INITIAL_GPS_PROVIDER_STATE && INITIAL_NETWORK_PROVIDER_STATE) { SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_HIGH_ACCURACY))145 SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_HIGH_ACCURACY)); SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("gps,network"))146 SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("gps,network")); 147 } else if (INITIAL_GPS_PROVIDER_STATE) { SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_SENSORS_ONLY))148 SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_SENSORS_ONLY)); SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("gps"))149 SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("gps")); 150 } else if (INITIAL_NETWORK_PROVIDER_STATE) { SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_BATTERY_SAVING))151 SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(Secure.LOCATION_MODE_BATTERY_SAVING)); SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("network"))152 SECURE_DEFAULTS.put(Secure.LOCATION_PROVIDERS_ALLOWED, Optional.of("network")); 153 } else { SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(LOCATION_MODE_OFF))154 SECURE_DEFAULTS.put(Secure.LOCATION_MODE, Optional.of(LOCATION_MODE_OFF)); 155 } 156 } 157 158 private static final Map<String, Optional<Object>> dataMap = 159 new ConcurrentHashMap<>(SECURE_DEFAULTS); 160 161 @Implementation(maxSdk = P) 162 @SuppressWarnings("robolectric.ShadowReturnTypeMismatch") setLocationProviderEnabledForUser( ContentResolver cr, String provider, boolean enabled, int uid)163 protected static boolean setLocationProviderEnabledForUser( 164 ContentResolver cr, String provider, boolean enabled, int uid) { 165 return updateEnabledProviders(cr, provider, enabled); 166 } 167 168 // only for use locally and by ShadowLocationManager, which requires a tight integration with 169 // ShadowSettings due to historical weirdness between LocationManager and Settings. updateEnabledProviders(ContentResolver cr, String provider, boolean enabled)170 static boolean updateEnabledProviders(ContentResolver cr, String provider, boolean enabled) { 171 Set<String> providers = new HashSet<>(); 172 String oldProviders = 173 Settings.Secure.getString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED); 174 if (!TextUtils.isEmpty(oldProviders)) { 175 providers.addAll(Arrays.asList(oldProviders.split(","))); 176 } 177 178 if (enabled == oldProviders.contains(provider)) { 179 return true; 180 } 181 182 if (enabled) { 183 providers.add(provider); 184 } else { 185 providers.remove(provider); 186 } 187 188 String newProviders = TextUtils.join(",", providers.toArray()); 189 boolean r = 190 Settings.Secure.putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders); 191 192 Intent providersBroadcast = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION); 193 if (RuntimeEnvironment.getApiLevel() >= Q) { 194 providersBroadcast.putExtra(LocationManager.EXTRA_PROVIDER_NAME, provider); 195 } 196 if (RuntimeEnvironment.getApiLevel() >= R) { 197 providersBroadcast.putExtra(LocationManager.EXTRA_PROVIDER_ENABLED, enabled); 198 } 199 RuntimeEnvironment.getApplication().sendBroadcast(providersBroadcast); 200 201 return r; 202 } 203 204 @Implementation putInt(ContentResolver cr, String name, int value)205 protected static boolean putInt(ContentResolver cr, String name, int value) { 206 boolean changed = !Objects.equals(dataMap.put(name, Optional.of(value)), Optional.of(value)); 207 208 if (Settings.Secure.LOCATION_MODE.equals(name)) { 209 if (RuntimeEnvironment.getApiLevel() <= P) { 210 // do this after setting location mode but before invoking contentobservers, so that 211 // observers for both settings will see the correct values 212 boolean gps = 213 (value == Settings.Secure.LOCATION_MODE_SENSORS_ONLY 214 || value == Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); 215 boolean network = 216 (value == Settings.Secure.LOCATION_MODE_BATTERY_SAVING 217 || value == Settings.Secure.LOCATION_MODE_HIGH_ACCURACY); 218 Settings.Secure.setLocationProviderEnabled(cr, LocationManager.GPS_PROVIDER, gps); 219 Settings.Secure.setLocationProviderEnabled(cr, LocationManager.NETWORK_PROVIDER, network); 220 } 221 222 Intent modeBroadcast = new Intent(LocationManager.MODE_CHANGED_ACTION); 223 if (RuntimeEnvironment.getApiLevel() >= R) { 224 modeBroadcast.putExtra( 225 LocationManager.EXTRA_LOCATION_ENABLED, value != LOCATION_MODE_OFF); 226 } 227 RuntimeEnvironment.getApplication().sendBroadcast(modeBroadcast); 228 } 229 230 if (changed && cr != null) { 231 cr.notifyChange(Settings.Secure.getUriFor(name), null); 232 } 233 234 return true; 235 } 236 237 @Implementation putIntForUser( ContentResolver cr, String name, int value, int userHandle)238 protected static boolean putIntForUser( 239 ContentResolver cr, String name, int value, int userHandle) { 240 putInt(cr, name, value); 241 return true; 242 } 243 244 @Implementation getIntForUser(ContentResolver cr, String name, int def, int userHandle)245 protected static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) { 246 // ignore userhandle 247 return getInt(cr, name, def); 248 } 249 250 @Implementation getIntForUser(ContentResolver cr, String name, int userHandle)251 protected static int getIntForUser(ContentResolver cr, String name, int userHandle) 252 throws SettingNotFoundException { 253 // ignore userhandle 254 return getInt(cr, name); 255 } 256 257 @Implementation getInt(ContentResolver cr, String name)258 protected static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { 259 if (Settings.Secure.LOCATION_MODE.equals(name) && RuntimeEnvironment.getApiLevel() < P) { 260 // Map from to underlying location provider storage API to location mode 261 return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0); 262 } 263 264 return get(Integer.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 265 } 266 267 @Implementation getInt(ContentResolver cr, String name, int def)268 protected static int getInt(ContentResolver cr, String name, int def) { 269 if (Settings.Secure.LOCATION_MODE.equals(name) && RuntimeEnvironment.getApiLevel() < P) { 270 // Map from to underlying location provider storage API to location mode 271 return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0); 272 } 273 274 return get(Integer.class, name).orElse(def); 275 } 276 277 @Implementation putString(ContentResolver cr, String name, String value)278 protected static boolean putString(ContentResolver cr, String name, String value) { 279 return put(cr, name, value); 280 } 281 282 @Implementation getString(ContentResolver cr, String name)283 protected static String getString(ContentResolver cr, String name) { 284 return get(String.class, name).orElse(null); 285 } 286 287 @Implementation getStringForUser(ContentResolver cr, String name, int userHandle)288 protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { 289 return getString(cr, name); 290 } 291 292 @Implementation putLong(ContentResolver cr, String name, long value)293 protected static boolean putLong(ContentResolver cr, String name, long value) { 294 return put(cr, name, value); 295 } 296 297 @Implementation getLong(ContentResolver cr, String name, long def)298 protected static long getLong(ContentResolver cr, String name, long def) { 299 return get(Long.class, name).orElse(def); 300 } 301 302 @Implementation getLong(ContentResolver cr, String name)303 protected static long getLong(ContentResolver cr, String name) throws SettingNotFoundException { 304 return get(Long.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 305 } 306 307 @Implementation putFloat(ContentResolver cr, String name, float value)308 protected static boolean putFloat(ContentResolver cr, String name, float value) { 309 return put(cr, name, value); 310 } 311 312 @Implementation getFloat(ContentResolver cr, String name, float def)313 protected static float getFloat(ContentResolver cr, String name, float def) { 314 return get(Float.class, name).orElse(def); 315 } 316 317 @Implementation getFloat(ContentResolver cr, String name)318 protected static float getFloat(ContentResolver cr, String name) 319 throws SettingNotFoundException { 320 return get(Float.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 321 } 322 put(ContentResolver cr, String name, Object value)323 private static boolean put(ContentResolver cr, String name, Object value) { 324 if (!Objects.equals( 325 dataMap.put(name, Optional.ofNullable(value)), Optional.ofNullable(value))) { 326 if (cr != null) { 327 cr.notifyChange(Settings.Secure.getUriFor(name), null); 328 } 329 } 330 return true; 331 } 332 get(Class<T> type, String name)333 private static <T> Optional<T> get(Class<T> type, String name) { 334 return dataMap.getOrDefault(name, Optional.empty()).filter(type::isInstance).map(type::cast); 335 } 336 remove(String name)337 public static void remove(String name) { 338 dataMap.remove(name); 339 } 340 341 @Resetter reset()342 public static void reset() { 343 dataMap.clear(); 344 dataMap.putAll(SECURE_DEFAULTS); 345 } 346 } 347 348 @Implements(value = Settings.Global.class) 349 public static class ShadowGlobal { 350 private static final ImmutableMap<String, Optional<Object>> DEFAULTS = 351 ImmutableMap.<String, Optional<Object>>builder() 352 .put(Settings.Global.ANIMATOR_DURATION_SCALE, Optional.of(1)) 353 .build(); 354 private static final Map<String, Optional<Object>> settings = new ConcurrentHashMap<>(DEFAULTS); 355 356 @Implementation putInt(ContentResolver cr, String name, int value)357 protected static boolean putInt(ContentResolver cr, String name, int value) { 358 return put(cr, name, value); 359 } 360 361 @Implementation getInt(ContentResolver cr, String name, int def)362 protected static int getInt(ContentResolver cr, String name, int def) { 363 return get(Integer.class, name).orElse(def); 364 } 365 366 @Implementation getInt(ContentResolver cr, String name)367 protected static int getInt(ContentResolver cr, String name) throws SettingNotFoundException { 368 return get(Integer.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 369 } 370 371 @Implementation putString(ContentResolver cr, String name, String value)372 protected static boolean putString(ContentResolver cr, String name, String value) { 373 return put(cr, name, value); 374 } 375 376 @Implementation getString(ContentResolver cr, String name)377 protected static String getString(ContentResolver cr, String name) { 378 return get(String.class, name).orElse(null); 379 } 380 381 @Implementation getStringForUser(ContentResolver cr, String name, int userHandle)382 protected static String getStringForUser(ContentResolver cr, String name, int userHandle) { 383 return getString(cr, name); 384 } 385 386 @Implementation putLong(ContentResolver cr, String name, long value)387 protected static boolean putLong(ContentResolver cr, String name, long value) { 388 return put(cr, name, value); 389 } 390 391 @Implementation getLong(ContentResolver cr, String name, long def)392 protected static long getLong(ContentResolver cr, String name, long def) { 393 return get(Long.class, name).orElse(def); 394 } 395 396 @Implementation getLong(ContentResolver cr, String name)397 protected static long getLong(ContentResolver cr, String name) throws SettingNotFoundException { 398 return get(Long.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 399 } 400 401 @Implementation putFloat(ContentResolver cr, String name, float value)402 protected static boolean putFloat(ContentResolver cr, String name, float value) { 403 boolean result = put(cr, name, value); 404 if (Settings.Global.ANIMATOR_DURATION_SCALE.equals(name)) { 405 ShadowValueAnimator.setDurationScale(value); 406 } 407 return result; 408 } 409 410 @Implementation getFloat(ContentResolver cr, String name, float def)411 protected static float getFloat(ContentResolver cr, String name, float def) { 412 return get(Float.class, name).orElse(def); 413 } 414 415 @Implementation getFloat(ContentResolver cr, String name)416 protected static float getFloat(ContentResolver cr, String name) 417 throws SettingNotFoundException { 418 return get(Float.class, name).orElseThrow(() -> new SettingNotFoundException(name)); 419 } 420 put(ContentResolver cr, String name, Object value)421 private static boolean put(ContentResolver cr, String name, Object value) { 422 if (!Objects.equals( 423 settings.put(name, Optional.ofNullable(value)), Optional.ofNullable(value))) { 424 if (cr != null) { 425 cr.notifyChange(Settings.Global.getUriFor(name), null); 426 } 427 } 428 return true; 429 } 430 get(Class<T> type, String name)431 private static <T> Optional<T> get(Class<T> type, String name) { 432 return settings.getOrDefault(name, Optional.empty()).filter(type::isInstance).map(type::cast); 433 } 434 435 @Resetter reset()436 public static void reset() { 437 settings.clear(); 438 settings.putAll(DEFAULTS); 439 } 440 } 441 442 /** 443 * Sets the value of the {@link Settings.System#AIRPLANE_MODE_ON} setting. 444 * 445 * @param isAirplaneMode new status for airplane mode 446 */ setAirplaneMode(boolean isAirplaneMode)447 public static void setAirplaneMode(boolean isAirplaneMode) { 448 Settings.Global.putInt( 449 RuntimeEnvironment.getApplication().getContentResolver(), 450 Settings.Global.AIRPLANE_MODE_ON, 451 isAirplaneMode ? 1 : 0); 452 Settings.System.putInt( 453 RuntimeEnvironment.getApplication().getContentResolver(), 454 Settings.System.AIRPLANE_MODE_ON, 455 isAirplaneMode ? 1 : 0); 456 } 457 458 /** 459 * Non-Android accessor that allows the value of the WIFI_ON setting to be set. 460 * 461 * @param isOn new status for wifi mode 462 */ setWifiOn(boolean isOn)463 public static void setWifiOn(boolean isOn) { 464 Settings.Global.putInt( 465 RuntimeEnvironment.getApplication().getContentResolver(), 466 Settings.Global.WIFI_ON, 467 isOn ? 1 : 0); 468 Settings.System.putInt( 469 RuntimeEnvironment.getApplication().getContentResolver(), 470 Settings.System.WIFI_ON, 471 isOn ? 1 : 0); 472 } 473 474 /** 475 * Sets the value of the {@link Settings.System#TIME_12_24} setting. 476 * 477 * @param use24HourTimeFormat new status for the time setting 478 */ set24HourTimeFormat(boolean use24HourTimeFormat)479 public static void set24HourTimeFormat(boolean use24HourTimeFormat) { 480 Settings.System.putString( 481 RuntimeEnvironment.getApplication().getContentResolver(), 482 Settings.System.TIME_12_24, 483 use24HourTimeFormat ? "24" : "12"); 484 } 485 486 private static boolean canDrawOverlays = false; 487 488 /** 489 * @return false by default, or the value specified via {@link #setCanDrawOverlays(boolean)} 490 */ 491 @Implementation(minSdk = M) canDrawOverlays(Context context)492 protected static boolean canDrawOverlays(Context context) { 493 return canDrawOverlays; 494 } 495 496 /** Sets the value returned by {@link #canDrawOverlays(Context)}. */ setCanDrawOverlays(boolean canDrawOverlays)497 public static void setCanDrawOverlays(boolean canDrawOverlays) { 498 ShadowSettings.canDrawOverlays = canDrawOverlays; 499 } 500 501 /** 502 * Sets the value of the {@link Settings.Global#ADB_ENABLED} setting or {@link 503 * Settings.Secure#ADB_ENABLED} depending on API level. 504 * 505 * @param adbEnabled new value for whether adb is enabled 506 */ setAdbEnabled(boolean adbEnabled)507 public static void setAdbEnabled(boolean adbEnabled) { 508 Settings.Global.putInt( 509 RuntimeEnvironment.getApplication().getContentResolver(), 510 Settings.Global.ADB_ENABLED, 511 adbEnabled ? 1 : 0); 512 // Support all clients by always setting the Secure version of the setting 513 Settings.Secure.putInt( 514 RuntimeEnvironment.getApplication().getContentResolver(), 515 Settings.Secure.ADB_ENABLED, 516 adbEnabled ? 1 : 0); 517 } 518 519 /** 520 * Sets the value of the {@link Settings.Global#INSTALL_NON_MARKET_APPS} setting or {@link 521 * Settings.Secure#INSTALL_NON_MARKET_APPS} depending on API level. 522 * 523 * @param installNonMarketApps new value for whether non-market apps are allowed to be installed 524 */ setInstallNonMarketApps(boolean installNonMarketApps)525 public static void setInstallNonMarketApps(boolean installNonMarketApps) { 526 // This setting moved from Secure to Global in JELLY_BEAN_MR1 and then moved it back to Global 527 // in LOLLIPOP. Support all clients by always setting this field on all versions >= 528 // JELLY_BEAN_MR1. 529 Settings.Global.putInt( 530 RuntimeEnvironment.getApplication().getContentResolver(), 531 Settings.Global.INSTALL_NON_MARKET_APPS, 532 installNonMarketApps ? 1 : 0); 533 // Always set the Secure version of the setting 534 Settings.Secure.putInt( 535 RuntimeEnvironment.getApplication().getContentResolver(), 536 Settings.Secure.INSTALL_NON_MARKET_APPS, 537 installNonMarketApps ? 1 : 0); 538 } 539 setLockScreenShowNotifications(boolean lockScreenShowNotifications)540 public static void setLockScreenShowNotifications(boolean lockScreenShowNotifications) { 541 Settings.Secure.putInt( 542 RuntimeEnvironment.getApplication().getContentResolver(), 543 Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 544 lockScreenShowNotifications ? 1 : 0); 545 } 546 setLockScreenAllowPrivateNotifications( boolean lockScreenAllowPrivateNotifications)547 public static void setLockScreenAllowPrivateNotifications( 548 boolean lockScreenAllowPrivateNotifications) { 549 Settings.Secure.putInt( 550 RuntimeEnvironment.getApplication().getContentResolver(), 551 Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 552 lockScreenAllowPrivateNotifications ? 1 : 0); 553 } 554 555 /** 556 * Shadow for {@link Settings.Config}. 557 * 558 * <p>This shadow is primarily to support {@link android.provider.DeviceConfig}, which queries 559 * {@link Settings.Config}. {@link android.provider.DeviceConfig} is pure Java code so it's not 560 * necessary to shadow that directly. 561 */ 562 @Implements(value = Settings.Config.class, isInAndroidSdk = false, minSdk = Q) 563 public static class ShadowConfig { 564 565 private static final Map<String, String> settings = new ConcurrentHashMap<>(); 566 567 @Implementation(maxSdk = Q) putString( ContentResolver cr, String name, String value, boolean makeDefault)568 protected static boolean putString( 569 ContentResolver cr, String name, String value, boolean makeDefault) { 570 return put(name, value); 571 } 572 573 @Implementation(minSdk = R, maxSdk = TIRAMISU) putString( ContentResolver cr, String namespace, String name, String value, boolean makeDefault)574 protected static boolean putString( 575 ContentResolver cr, String namespace, String name, String value, boolean makeDefault) { 576 String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); 577 return put(key, value); 578 } 579 580 @Implementation(minSdk = U.SDK_INT) putString( String namespace, String name, String value, boolean makeDefault)581 protected static boolean putString( 582 String namespace, String name, String value, boolean makeDefault) { 583 String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); 584 return put(key, value); 585 } 586 587 @Implementation(maxSdk = TIRAMISU) getString(ContentResolver cr, String name)588 protected static String getString(ContentResolver cr, String name) { 589 return get(name); 590 } 591 592 @Implementation(minSdk = U.SDK_INT) getString(String name)593 protected static String getString(String name) { 594 return get(name); 595 } 596 597 @Implementation(minSdk = R) getStrings( ContentResolver resolver, String namespace, List<String> names)598 protected static Map<String, String> getStrings( 599 ContentResolver resolver, String namespace, List<String> names) { 600 601 Map<String, String> result = new HashMap<>(); 602 for (Map.Entry<String, String> entry : settings.entrySet()) { 603 String key = entry.getKey(); 604 if (!key.startsWith(namespace + "/")) { 605 continue; 606 } 607 String keyWithoutNamespace = key.substring(namespace.length() + 1); 608 if (names == null || names.isEmpty()) { 609 result.put(keyWithoutNamespace, entry.getValue()); 610 } else if (names.contains(keyWithoutNamespace)) { 611 result.put(keyWithoutNamespace, entry.getValue()); 612 } 613 } 614 return ImmutableMap.copyOf(result); 615 } 616 put(String name, String value)617 private static boolean put(String name, String value) { 618 settings.put(name, value); 619 return true; 620 } 621 622 @Implementation(minSdk = R) setStrings( ContentResolver cr, String namespace, Map<String, String> keyValues)623 protected static boolean setStrings( 624 ContentResolver cr, String namespace, Map<String, String> keyValues) { 625 626 synchronized (settings) { 627 settings.entrySet().removeIf(entry -> entry.getKey().startsWith(namespace + "/")); 628 for (Map.Entry<String, String> entry : keyValues.entrySet()) { 629 String key = 630 reflector(SettingsConfigReflector.class) 631 .createCompositeName(namespace, entry.getKey()); 632 put(key, entry.getValue()); 633 } 634 } 635 return true; 636 } 637 638 @Implementation(minSdk = U.SDK_INT) deleteString(String namespace, String name)639 protected static boolean deleteString(String namespace, String name) { 640 String key = reflector(SettingsConfigReflector.class).createCompositeName(namespace, name); 641 settings.remove(key); 642 return true; 643 } 644 645 @Implementation(minSdk = TIRAMISU, maxSdk = TIRAMISU) deleteString(ContentResolver resolver, String namespace, String name)646 protected static boolean deleteString(ContentResolver resolver, String namespace, String name) { 647 return deleteString(namespace, name); 648 } 649 get(String name)650 private static String get(String name) { 651 return settings.get(name); 652 } 653 654 @Resetter reset()655 public static void reset() { 656 settings.clear(); 657 } 658 } 659 660 @Resetter reset()661 public static void reset() { 662 canDrawOverlays = false; 663 } 664 665 @ForType(Settings.Secure.class) 666 interface SettingsSecureReflector { 667 @Static getLocationModeForUser(ContentResolver cr, int userId)668 int getLocationModeForUser(ContentResolver cr, int userId); 669 } 670 671 @ForType(Settings.Config.class) 672 interface SettingsConfigReflector { 673 @Static createCompositeName(String namespace, String name)674 String createCompositeName(String namespace, String name); 675 } 676 } 677