Skip to content
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ Each URL is in the form _file:///path/to/spot/_, and can be converted to a
* `cordova.file.externalCacheDirectory` - Application cache on external storage.
(_Android_). See [Quirks](#androids-external-storage-quirks).

* `cordova.file.externalMediaDirectory` - Application media directory on external storage. (_Android_)

* `cordova.file.externalRootDirectory` - External storage (SD card) root. (_Android_, _BlackBerry 10_). See [Quirks](#androids-external-storage-quirks).

* `cordova.file.tempDirectory` - Temp directory that the OS can clear at will. Do not
Expand All @@ -117,6 +119,14 @@ Each URL is in the form _file:///path/to/spot/_, and can be converted to a

* `cordova.file.sharedDirectory` - Files globally available to all applications (_BlackBerry 10_)

* `cordova.file.removableExternalApplicationStorageDirectories` - Application space on removable external storages. (_Android_)

* `cordova.file.removableExternalDataDirectories` - Where to put app-specific data files on removable external storages. (_Android_)

* `cordova.file.removableExternalCacheDirectories` - Application cache on removable external storages. (_Android_)

* `cordova.file.removableExternalMediaDirectories` - Application media directory on removable external storages. (_Android_)

## File System Layouts

Although technically an implementation detail, it can be very useful to know how
Expand Down Expand Up @@ -156,14 +166,20 @@ the `cordova.file.*` properties map to physical paths on a real device.
| Device Path | `cordova.file.*` | `AndroidExtraFileSystems` | r/w? | persistent? | OS clears | private |
|:------------------------------------------------|:----------------------------|:--------------------------|:----:|:-----------:|:---------:|:-------:|
| `file:///android_asset/` | applicationDirectory | assets | r | N/A | N/A | Yes |
| `/data/data/<app-id>/` | applicationStorageDirectory | - | r/w | N/A | N/A | Yes |
| `<internal>/<app-id>/` | applicationStorageDirectory | - | r/w | N/A | N/A | Yes |
| &nbsp;&nbsp;&nbsp;`cache` | cacheDirectory | cache | r/w | Yes | Yes\* | Yes |
| &nbsp;&nbsp;&nbsp;`files` | dataDirectory | files | r/w | Yes | No | Yes |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`Documents` | | documents | r/w | Yes | No | Yes |
| `<sdcard>/` | externalRootDirectory | sdcard | r/w\*\*\* | Yes | No | No |
| &nbsp;&nbsp;&nbsp;`Android/data/<app-id>/` | externalApplicationStorageDirectory | - | r/w | Yes | No | No |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cache` | externalCacheDirectory | cache-external | r/w | Yes | No\*\*| No |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cache` | externalCacheDirectory | cache-external | r/w | Yes | No\*\*| No |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`files` | externalDataDirectory | files-external | r/w | Yes | No | No |
| &nbsp;&nbsp;&nbsp;`Android/media/<app-id>/` | externalMediaDirectory | - | r/w | Yes | No | No |
| `<removable sdcard>/` (0 or more) | | - | r/w\*\*\* | Yes | No | No |
| &nbsp;&nbsp;&nbsp;`Android/data/<app-id>/` | removableExternalApplicationStorageDirectories | - | r/w | Yes | No | No |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`cache` | removableExternalCacheDirectories | - | r/w | Yes | No\*\*| No |
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`files` | removableExternalDataDirectories | - | r/w | Yes | No | No |
| &nbsp;&nbsp;&nbsp;`Android/media/<app-id>/` | removableExternalMediaDirectories | - | r/w | Yes | No | No |

\* The OS may periodically clear this directory, but do not rely on this behavior. Clear
the contents of this directory as appropriate for your application. Should a user
Expand Down
65 changes: 60 additions & 5 deletions src/android/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,21 @@ private boolean needPermission(String nativeURL, int permissionType) throws JSON
if (j.has("externalApplicationStorageDirectory")) {
allowedStorageDirectories.add(j.getString("externalApplicationStorageDirectory"));
}
if (j.has("removableExternalApplicationStorageDirectories")) {
JSONArray array = j.getJSONArray("removableExternalApplicationStorageDirectories");
for (int i = 0; i < array.length(); i++) {
allowedStorageDirectories.add(array.getString(i));
}
}
if (j.has("removableExternalMediaDirectories")) {
JSONArray array = j.getJSONArray("removableExternalMediaDirectories");
for (int i = 0; i < array.length(); i++) {
allowedStorageDirectories.add(array.getString(i));
}
}
if (j.has("externalMediaDirectory")) {
allowedStorageDirectories.add(j.getString("externalMediaDirectory"));
}

if (permissionType == READ && hasReadPermission()) {
return false;
Expand Down Expand Up @@ -998,16 +1013,56 @@ private JSONObject requestAllPaths() throws JSONException {
ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
try {
try {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
} catch (NullPointerException e) {
/* If external storage is unavailable, context.getExternal* returns null */
LOG.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
}

JSONArray removableExternalApplicationStorageDirs = new JSONArray();
JSONArray removableExternalDataDirs = new JSONArray();
JSONArray removableExternalCacheDirs = new JSONArray();
JSONArray removableExternalMediaDirs = new JSONArray();
String externalMediaDir = null;
for (File filesDir : context.getExternalFilesDirs(null)) {
if (filesDir != null) {
if (Environment.isExternalStorageRemovable(filesDir)) {
removableExternalApplicationStorageDirs.put(toDirUrl(filesDir.getParentFile()));
removableExternalDataDirs.put(toDirUrl(filesDir));
}
}
}
for (File cacheDir : context.getExternalCacheDirs()) {
if (cacheDir != null) {
if (Environment.isExternalStorageRemovable(cacheDir)) {
removableExternalCacheDirs.put(toDirUrl(cacheDir));
}
}
}
for (File mediaDir : context.getExternalMediaDirs()) {
if (mediaDir != null) {
String dirUrl = toDirUrl(mediaDir);
if (Environment.isExternalStorageRemovable(mediaDir)) {
removableExternalMediaDirs.put(dirUrl);
} else {
if (externalMediaDir != null) {
LOG.w(LOG_TAG, "External media directory already found ; skip other value " + dirUrl);
continue;
}
externalMediaDir = dirUrl;
}
}
}
ret.put("removableExternalApplicationStorageDirectories", removableExternalApplicationStorageDirs);
ret.put("removableExternalDataDirectories", removableExternalDataDirs);
ret.put("removableExternalCacheDirectories", removableExternalCacheDirs);
ret.put("removableExternalMediaDirectories", removableExternalMediaDirs);
ret.put("externalMediaDirectory", externalMediaDir);
} catch (NullPointerException e) {
/* If external storage is unavailable, context.getExternal* returns null */
LOG.d(LOG_TAG, "Unable to access these paths, most likely due to USB storage");
}
return ret;
}
Expand Down