• 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.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