• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getExternalStorageState()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    */
setExternalStorageState(String externalStorageState)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    */
setIsExternalStorageEmulated(boolean emulated)55   public static void setIsExternalStorageEmulated(boolean emulated) {
56     ShadowEnvironment.sIsExternalStorageEmulated = emulated;
57   }
58 
59   @Implementation
getExternalStorageDirectory()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
getExternalStoragePublicDirectory(String type)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
reset()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 
exists(Path path)93   private static boolean exists(Path path) {
94     return path != null && Files.exists(path);
95   }
96 
97   @Implementation
isExternalStorageRemovable()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)
getStorageState(File directory)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)
getExternalStorageState(File directory)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)
isExternalStorageRemovable(File path)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)
isExternalStorageEmulated(File path)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
isExternalStorageEmulated()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    */
setExternalStorageRemovable(File file, boolean isRemovable)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    */
setExternalStorageEmulated(File file, boolean isEmulated)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    */
addExternalDir(String path)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    */
setExternalStorageState(File directory, String state)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)
getExternalDirs()219     protected File[] getExternalDirs() {
220       return externalDirs.toArray(new File[externalDirs.size()]);
221     }
222   }
223 }
224