Skip to content

Commit df9893d

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents f2e7cae + 265a932 commit df9893d

File tree

13 files changed

+111
-87
lines changed

13 files changed

+111
-87
lines changed

.asf.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,32 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
github:
19+
description: Apache Cordova File Plugin
20+
homepage: https://cordova.apache.org/
21+
22+
labels:
23+
- android
24+
- cordova
25+
- hacktoberfest
26+
- ios
27+
- java
28+
- javascript
29+
- library
30+
- mobile
31+
- nodejs
32+
- objective-c
33+
34+
features:
35+
wiki: false
36+
issues: true
37+
projects: true
38+
39+
enabled_merge_buttons:
40+
squash: true
41+
merge: false
42+
rebase: false
43+
1844
notifications:
1945
commits: commits@cordova.apache.org
2046
issues: issues@cordova.apache.org

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ Each URL is in the form _file:///path/to/spot/_, and can be converted to a
9595
in here. (_iOS_, _Android_, _BlackBerry 10_, _OSX_, _windows_)
9696

9797
* `cordova.file.externalApplicationStorageDirectory` - Application space on
98-
external storage. (_Android_)
98+
external storage. (_Android_). See [Quirks](#androids-external-storage-quirks).
9999

100100
* `cordova.file.externalDataDirectory` - Where to put app-specific data files on
101-
external storage. (_Android_)
101+
external storage. (_Android_). See [Quirks](#androids-external-storage-quirks).
102102

103103
* `cordova.file.externalCacheDirectory` - Application cache on external storage.
104-
(_Android_)
104+
(_Android_). See [Quirks](#androids-external-storage-quirks).
105105

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

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

110110
* `cordova.file.tempDirectory` - Temp directory that the OS can clear at will. Do not
111111
rely on the OS to clear this directory; your app should always remove files as
@@ -194,6 +194,26 @@ the `cordova.file.*` properties map to physical paths on a real device.
194194
**Note**: If external storage can't be mounted, the `cordova.file.external*`
195195
properties are `null`.
196196

197+
#### Android's External Storage Quirks
198+
199+
With the introduction of [Scoped Storage](https://source.android.com/docs/core/storage/scoped) access to External Storage is unreliable or limited via File APIs.
200+
Scoped Storage was introduced in API 29. While existing apps may have the ability to opt out, this option is not available for new apps. On Android API 30 and later, Scoped Storage is fully enforced.
201+
202+
Additionally, Direct File Access **is not** supported on API 29. This means this plugin **cannot** access external storage mediums on API 29 devices.
203+
204+
API 30 introduced [FUSE](https://source.android.com/docs/core/storage/scoped) which allowed limited access to external storage using File APIs, allowing this plugin to
205+
partially work again.
206+
207+
Limited access includes but isn't limited to:
208+
- Read only access with appropriate `READ_EXTERNAL` or [READ_MEDIA_*](https://developer.android.com/training/data-storage/shared/media#access-other-apps-files) permissions.
209+
- Read only access is limited to media files, but not documents.
210+
- Writes are limited to only files owned by your app. Modifying files owned by a third-party app (including an image file created via the camera plugin for example) is not possible via the File API.
211+
- Not all paths in external storage is writable.
212+
213+
These limitations only applies to external filesystems (e.g. `cordova.file.external*` paths). Internal filesystems such as `cordova.file.dataDirectory` path are not imposed by these limitations.
214+
215+
If interfacing with the external file system is a requirement for your application, consider using a [MediaStore](https://www.npmjs.com/search?q=ecosystem%3Acordova%20storage%20access%20framework) plugin instead.
216+
197217
### OS X File System Layout
198218

199219
| Device Path | `cordova.file.*` | `iosExtraFileSystems` | r/w? | OS clears | private |

RELEASENOTES.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
-->
2121
# Release Notes
2222

23+
### 8.0.1 (Oct 27, 2023)
24+
25+
**Fixes:**
26+
27+
* [GH-608](https://github.com/apache/cordova-plugin-file/pull/608) fix(android): `hasWritePermission` for SDK 33
28+
29+
**Others:**
30+
31+
* [GH-609](https://github.com/apache/cordova-plugin-file/pull/609) chore: update asf config
32+
* [GH-607](https://github.com/apache/cordova-plugin-file/pull/607) refactor(android): various cleanup
33+
* [GH-593](https://github.com/apache/cordova-plugin-file/pull/593) doc(android): expanded on external filesystems limitations
34+
2335
### 8.0.0 (Jul 07, 2023)
2436

2537
* [GH-534](https://github.com/apache/cordova-plugin-file/pull/534) fix(android): `FileError` on a content `resolveLocalFileSystemURL`

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ns0m/cordova-plugin-file",
3-
"version": "8.0.1-4",
3+
"version": "8.0.2-dev",
44
"description": "Cordova File Plugin",
55
"types": "./types/index.d.ts",
66
"cordova": {

plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
2222
xmlns:android="http://schemas.android.com/apk/res/android"
2323
id="cordova-plugin-file"
24-
version="8.0.1-dev">
24+
version="8.0.2-dev">
2525
<name>File</name>
2626
<description>Cordova File Plugin</description>
2727
<license>Apache 2.0</license>

src/android/AssetFilesystem.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ Licensed to the Apache Software Foundation (ASF) under one
2424
import org.apache.cordova.CordovaPreferences;
2525
import org.apache.cordova.CordovaResourceApi;
2626
import org.apache.cordova.LOG;
27-
import org.json.JSONArray;
2827
import org.json.JSONException;
2928
import org.json.JSONObject;
3029

src/android/ContentFilesystem.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public LocalFilesystemURL toLocalUri(Uri inputURL) {
8686

8787
@Override
8888
public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
89-
String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
89+
String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
9090
throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
9191
}
9292

@@ -163,10 +163,9 @@ public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws
163163

164164
@Override
165165
public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
166-
int offset, boolean isBinary) throws NoModificationAllowedException {
166+
int offset, boolean isBinary) throws NoModificationAllowedException {
167167
throw new NoModificationAllowedException("Couldn't write to file given its content URI");
168168
}
169-
170169
@Override
171170
public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
172171
throws NoModificationAllowedException {

src/android/FileUtils.java

Lines changed: 22 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ Licensed to the Apache Software Foundation (ASF) under one
3939
import org.apache.cordova.LOG;
4040
import org.apache.cordova.PermissionHelper;
4141
import org.apache.cordova.PluginResult;
42-
4342
import org.json.JSONArray;
4443
import org.json.JSONException;
4544
import org.json.JSONObject;
@@ -543,10 +542,21 @@ private void getReadPermission(String rawArgs, int action, CallbackContext callb
543542
}
544543

545544
private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
546-
int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
547-
PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
545+
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
546+
int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
547+
PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
548+
}
548549
}
549550

551+
/**
552+
* If your app targets Android 13 (SDK 33) or higher and needs to access media files that other apps have created,
553+
* you must request one or more of the following granular media permissions READ_MEDIA_*
554+
* instead of the READ_EXTERNAL_STORAGE permission:
555+
*
556+
* Refer to: https://developer.android.com/about/versions/13/behavior-changes-13
557+
*
558+
* @return
559+
*/
550560
private boolean hasReadPermission() {
551561
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
552562
return PermissionHelper.hasPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
@@ -558,7 +568,10 @@ private boolean hasReadPermission() {
558568
}
559569

560570
private boolean hasWritePermission() {
561-
return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
571+
// Starting with API 33, requesting WRITE_EXTERNAL_STORAGE is an auto permission rejection
572+
return android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
573+
? true
574+
: PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
562575
}
563576

564577
private boolean needPermission(String nativeURL, int permissionType) throws JSONException {
@@ -685,8 +698,6 @@ public void run() {
685698
callbackContext.error(FileUtils.ENCODING_ERR);
686699
} else if (e instanceof IOException) {
687700
callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
688-
} else if (e instanceof EncodingException) {
689-
callbackContext.error(FileUtils.ENCODING_ERR);
690701
} else if (e instanceof TypeMismatchException) {
691702
callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
692703
} else if (e instanceof JSONException) {
@@ -1001,56 +1012,16 @@ private JSONObject requestAllPaths() throws JSONException {
10011012
ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
10021013
ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
10031014
ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
1004-
try {
1005-
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1015+
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1016+
try {
10061017
ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
10071018
ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
10081019
ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
10091020
ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
1021+
} catch (NullPointerException e) {
1022+
/* If external storage is unavailable, context.getExternal* returns null */
1023+
LOG.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
10101024
}
1011-
1012-
JSONArray removableExternalApplicationStorageDirs = new JSONArray();
1013-
JSONArray removableExternalDataDirs = new JSONArray();
1014-
JSONArray removableExternalCacheDirs = new JSONArray();
1015-
JSONArray removableExternalMediaDirs = new JSONArray();
1016-
String externalMediaDir = null;
1017-
for (File filesDir : context.getExternalFilesDirs(null)) {
1018-
if (filesDir != null) {
1019-
if (Environment.isExternalStorageRemovable(filesDir)) {
1020-
removableExternalApplicationStorageDirs.put(toDirUrl(filesDir.getParentFile()));
1021-
removableExternalDataDirs.put(toDirUrl(filesDir));
1022-
}
1023-
}
1024-
}
1025-
for (File cacheDir : context.getExternalCacheDirs()) {
1026-
if (cacheDir != null) {
1027-
if (Environment.isExternalStorageRemovable(cacheDir)) {
1028-
removableExternalCacheDirs.put(toDirUrl(cacheDir));
1029-
}
1030-
}
1031-
}
1032-
for (File mediaDir : context.getExternalMediaDirs()) {
1033-
if (mediaDir != null) {
1034-
String dirUrl = toDirUrl(mediaDir);
1035-
if (Environment.isExternalStorageRemovable(mediaDir)) {
1036-
removableExternalMediaDirs.put(dirUrl);
1037-
} else {
1038-
if (externalMediaDir != null) {
1039-
LOG.w(LOG_TAG, "External media directory already found ; skip other value " + dirUrl);
1040-
continue;
1041-
}
1042-
externalMediaDir = dirUrl;
1043-
}
1044-
}
1045-
}
1046-
ret.put("removableExternalApplicationStorageDirectories", removableExternalApplicationStorageDirs);
1047-
ret.put("removableExternalDataDirectories", removableExternalDataDirs);
1048-
ret.put("removableExternalCacheDirectories", removableExternalCacheDirs);
1049-
ret.put("removableExternalMediaDirectories", removableExternalMediaDirs);
1050-
ret.put("externalMediaDirectory", externalMediaDir);
1051-
} catch (NullPointerException e) {
1052-
/* If external storage is unavailable, context.getExternal* returns null */
1053-
LOG.d(LOG_TAG, "Unable to access these paths, most likely due to USB storage");
10541025
}
10551026
return ret;
10561027
}

src/android/Filesystem.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public JSONObject makeEntryForFile(File file) {
106106
}
107107

108108
abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
109-
JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
109+
JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
110110

111111
abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
112112

@@ -295,7 +295,7 @@ public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
295295
}
296296

297297
abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
298-
boolean isBinary) throws NoModificationAllowedException, IOException;
298+
boolean isBinary) throws NoModificationAllowedException, IOException;
299299

300300
abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
301301
throws IOException, NoModificationAllowedException;

0 commit comments

Comments
 (0)