Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
      4 import static android.os.Build.VERSION_CODES.KITKAT;
      5 import static android.os.Build.VERSION_CODES.LOLLIPOP;
      6 import static android.os.Build.VERSION_CODES.M;
      7 
      8 import android.os.Environment;
      9 import java.io.File;
     10 import java.io.IOException;
     11 import java.nio.file.Files;
     12 import java.nio.file.Path;
     13 import java.util.ArrayList;
     14 import java.util.HashMap;
     15 import java.util.List;
     16 import java.util.Map;
     17 import org.robolectric.RuntimeEnvironment;
     18 import org.robolectric.annotation.Implementation;
     19 import org.robolectric.annotation.Implements;
     20 import org.robolectric.annotation.Resetter;
     21 import org.robolectric.util.ReflectionHelpers;
     22 
     23 @Implements(Environment.class)
     24 public class ShadowEnvironment {
     25   private static String externalStorageState = Environment.MEDIA_REMOVED;
     26   private static final Map<File, Boolean> STORAGE_EMULATED = new HashMap<>();
     27   private static final Map<File, Boolean> STORAGE_REMOVABLE = new HashMap<>();
     28   private static boolean sIsExternalStorageEmulated;
     29   private static Path tmpExternalFilesDirBase;
     30   private static final List<File> externalDirs = new ArrayList<>();
     31   private static Map<Path, String> storageState = new HashMap<>();
     32 
     33   static Path EXTERNAL_CACHE_DIR;
     34   static Path EXTERNAL_FILES_DIR;
     35 
     36   @Implementation
     37   protected static String getExternalStorageState() {
     38     return externalStorageState;
     39   }
     40 
     41   /**
     42    * Sets the return value of {@link #getExternalStorageState()}.
     43    *
     44    * @param externalStorageState Value to return from {@link #getExternalStorageState()}.
     45    */
     46   public static void setExternalStorageState(String externalStorageState) {
     47     ShadowEnvironment.externalStorageState = externalStorageState;
     48   }
     49 
     50   /**
     51    * Sets the return value of {@link #isExternalStorageEmulated()}.
     52    *
     53    * @param emulated Value to return from {@link #isExternalStorageEmulated()}.
     54    */
     55   public static void setIsExternalStorageEmulated(boolean emulated) {
     56     ShadowEnvironment.sIsExternalStorageEmulated = emulated;
     57   }
     58 
     59   @Implementation
     60   protected static File getExternalStorageDirectory() {
     61     if (!exists(EXTERNAL_CACHE_DIR)) EXTERNAL_CACHE_DIR = RuntimeEnvironment.getTempDirectory().create("external-cache");
     62     return EXTERNAL_CACHE_DIR.toFile();
     63   }
     64 
     65   @Implementation
     66   protected static File getExternalStoragePublicDirectory(String type) {
     67     if (!exists(EXTERNAL_FILES_DIR)) EXTERNAL_FILES_DIR = RuntimeEnvironment.getTempDirectory().create("external-files");
     68     if (type == null) return EXTERNAL_FILES_DIR.toFile();
     69     Path path = EXTERNAL_FILES_DIR.resolve(type);
     70     try {
     71       Files.createDirectories(path);
     72     } catch (IOException e) {
     73       throw new RuntimeException(e);
     74     }
     75     return path.toFile();
     76   }
     77 
     78   @Resetter
     79   public static void reset() {
     80 
     81     EXTERNAL_CACHE_DIR = null;
     82     EXTERNAL_FILES_DIR = null;
     83 
     84     STORAGE_EMULATED.clear();
     85     STORAGE_REMOVABLE.clear();
     86 
     87     storageState = new HashMap<>();
     88     externalDirs.clear();
     89 
     90     sIsExternalStorageEmulated = false;
     91   }
     92 
     93   private static boolean exists(Path path) {
     94     return path != null && Files.exists(path);
     95   }
     96 
     97   @Implementation
     98   protected static boolean isExternalStorageRemovable() {
     99     final Boolean exists = STORAGE_REMOVABLE.get(getExternalStorageDirectory());
    100     return exists != null ? exists : false;
    101   }
    102 
    103   @Implementation(minSdk = KITKAT)
    104   protected static String getStorageState(File directory) {
    105     Path directoryPath = directory.toPath();
    106     for (Map.Entry<Path, String> entry : storageState.entrySet()) {
    107       if (directoryPath.startsWith(entry.getKey())) {
    108         return entry.getValue();
    109       }
    110     }
    111     return null;
    112   }
    113 
    114   @Implementation(minSdk = LOLLIPOP)
    115   protected static String getExternalStorageState(File directory) {
    116     Path directoryPath = directory.toPath();
    117     for (Map.Entry<Path, String> entry : storageState.entrySet()) {
    118       if (directoryPath.startsWith(entry.getKey())) {
    119         return entry.getValue();
    120       }
    121     }
    122     return null;
    123   }
    124 
    125   @Implementation(minSdk = LOLLIPOP)
    126   protected static boolean isExternalStorageRemovable(File path) {
    127     final Boolean exists = STORAGE_REMOVABLE.get(path);
    128     return exists != null ? exists : false;
    129   }
    130 
    131   @Implementation(minSdk = LOLLIPOP)
    132   protected static boolean isExternalStorageEmulated(File path) {
    133     final Boolean emulated = STORAGE_EMULATED.get(path);
    134     return emulated != null ? emulated : false;
    135   }
    136 
    137   @Implementation
    138   protected static boolean isExternalStorageEmulated() {
    139     return sIsExternalStorageEmulated;
    140   }
    141 
    142   /**
    143    * Sets the "isRemovable" flag of a particular file.
    144    *
    145    * @param file Target file.
    146    * @param isRemovable True if the filesystem is removable.
    147    */
    148   public static void setExternalStorageRemovable(File file, boolean isRemovable) {
    149     STORAGE_REMOVABLE.put(file, isRemovable);
    150   }
    151 
    152   /**
    153    * Sets the "isEmulated" flag of a particular file.
    154    *
    155    * @param file Target file.
    156    * @param isEmulated True if the filesystem is emulated.
    157    */
    158   public static void setExternalStorageEmulated(File file, boolean isEmulated) {
    159     STORAGE_EMULATED.put(file, isEmulated);
    160   }
    161 
    162   /**
    163    * Adds a directory to list returned by {@link ShadowUserEnvironment#getExternalDirs()}.
    164    *
    165    * @param path the external dir to add
    166    */
    167   public static File addExternalDir(String path) {
    168     Path externalFileDir;
    169     if (path == null) {
    170       externalFileDir = null;
    171     } else {
    172       try {
    173         if (tmpExternalFilesDirBase == null) {
    174           tmpExternalFilesDirBase = RuntimeEnvironment.getTempDirectory().create("external-files-base");
    175         }
    176         externalFileDir = tmpExternalFilesDirBase.resolve(path);
    177         Files.createDirectories(externalFileDir);
    178         externalDirs.add(externalFileDir.toFile());
    179       } catch (IOException e) {
    180         throw new RuntimeException("Could not create external files dir", e);
    181       }
    182     }
    183 
    184     if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1
    185         && RuntimeEnvironment.getApiLevel() < KITKAT) {
    186       if (externalDirs.size() == 1 && externalFileDir != null) {
    187         Environment.UserEnvironment userEnvironment =
    188             ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser");
    189         ReflectionHelpers.setField(
    190             userEnvironment, "mExternalStorageAndroidData", externalFileDir.toFile());
    191       }
    192     } else if (RuntimeEnvironment.getApiLevel() >= KITKAT && RuntimeEnvironment.getApiLevel() < M) {
    193       Environment.UserEnvironment userEnvironment =
    194           ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser");
    195       ReflectionHelpers.setField(userEnvironment, "mExternalDirsForApp",
    196           externalDirs.toArray(new File[externalDirs.size()]));
    197     }
    198 
    199     if (externalFileDir == null) {
    200       return null;
    201     }
    202     return externalFileDir.toFile();
    203   }
    204 
    205   /**
    206    * Sets the {@link #getExternalStorageState(File)} for given directory.
    207    *
    208    * @param externalStorageState Value to return from {@link #getExternalStorageState(File)}.
    209    */
    210   public static void setExternalStorageState(File directory, String state) {
    211     storageState.put(directory.toPath(), state);
    212   }
    213 
    214   @Implements(className = "android.os.Environment$UserEnvironment", isInAndroidSdk = false,
    215       minSdk = JELLY_BEAN_MR1)
    216   public static class ShadowUserEnvironment {
    217 
    218     @Implementation(minSdk = M)
    219     protected File[] getExternalDirs() {
    220       return externalDirs.toArray(new File[externalDirs.size()]);
    221     }
    222   }
    223 }
    224