Home | History | Annotate | Download | only in shadows
      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
     35     public static void reset() {
     36       dataMap.clear();
     37     }
     38 
     39     @Implementation(minSdk = JELLY_BEAN_MR1)
     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)
     46     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
     47       return getString(cr, name);
     48     }
     49 
     50     @Implementation
     51     protected static boolean putString(ContentResolver cr, String name, String value) {
     52       get(cr).put(name, value);
     53       return true;
     54     }
     55 
     56     @Implementation
     57     protected static String getString(ContentResolver cr, String name) {
     58       return get(cr).get(name);
     59     }
     60 
     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
     76     public static void reset() {
     77       dataMap.clear();
     78     }
     79 
     80     @Implementation(minSdk = JELLY_BEAN_MR1)
     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)
     87     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
     88       return getString(cr, name);
     89     }
     90 
     91     @Implementation
     92     protected static boolean putString(ContentResolver cr, String name, String value) {
     93       get(cr).put(name, value);
     94       return true;
     95     }
     96 
     97     @Implementation
     98     protected static String getString(ContentResolver cr, String name) {
     99       return get(cr).get(name);
    100     }
    101 
    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)
    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)
    118     protected static void setLocationProviderEnabled(
    119         ContentResolver cr, String provider, boolean enabled) {
    120       updateEnabledProviders(cr, provider, enabled);
    121     }
    122 
    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
    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
    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
    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
    211     public static void reset() {
    212       dataMap.clear();
    213     }
    214 
    215     @Implementation(minSdk = JELLY_BEAN_MR1)
    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)
    222     protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
    223       return getString(cr, name);
    224     }
    225 
    226     @Implementation
    227     protected static boolean putString(ContentResolver cr, String name, String value) {
    228       get(cr).put(name, value);
    229       return true;
    230     }
    231 
    232     @Implementation
    233     protected static String getString(ContentResolver cr, String name) {
    234       return get(cr).get(name);
    235     }
    236 
    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
    253     public static void reset() {
    254       dataMap.clear();
    255     }
    256 
    257     @Implementation
    258     protected static boolean putString(ContentResolver cr, String name, String value) {
    259       get(cr).put(name, value);
    260       return true;
    261     }
    262 
    263     @Implementation
    264     protected static String getString(ContentResolver cr, String name) {
    265       return get(cr).get(name);
    266     }
    267 
    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    */
    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    */
    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    */
    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)
    320   protected static boolean canDrawOverlays(Context context) {
    321     return canDrawOverlays;
    322   }
    323 
    324   /** Sets the value returned by {@link #canDrawOverlays(Context)}. */
    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    */
    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    */
    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
    374   public static void reset() {
    375     canDrawOverlays = false;
    376   }
    377 }
    378