diff --git a/filesystem/README.md b/filesystem/README.md index 41aa452f0..8b606b615 100644 --- a/filesystem/README.md +++ b/filesystem/README.md @@ -97,6 +97,7 @@ const readFilePath = async () => { * [`stat(...)`](#stat) * [`rename(...)`](#rename) * [`copy(...)`](#copy) +* [`isPortableStorageAvailable()`](#isportablestorageavailable) * [`checkPermissions()`](#checkpermissions) * [`requestPermissions()`](#requestpermissions) * [`downloadFile(...)`](#downloadfile) @@ -309,6 +310,22 @@ Copy a file or directory -------------------- +### isPortableStorageAvailable() + +```typescript +isPortableStorageAvailable() => Promise +``` + +Check if portable storage is available (SD Card for Android) +Only available on Android + +**Returns:** Promise<isPortableStorageAvailableResult> + +**Since:** 5.0.5 + +-------------------- + + ### checkPermissions() ```typescript @@ -533,6 +550,13 @@ Add a listener to file download progress events. | **`uri`** | string | The uri where the file was copied into | 4.0.0 | +#### isPortableStorageAvailableResult + +| Prop | Type | Description | Since | +| --------------- | -------------------- | ----------------------------------------------------------------- | ----- | +| **`available`** | boolean | Is portable storage available (SD Card) Only available on Android | 5.0.5 | + + #### PermissionStatus | Prop | Type | @@ -607,6 +631,8 @@ A listener function that receives progress events. | **`Cache`** | 'CACHE' | The Cache directory. Can be deleted in cases of low memory, so use this directory to write app-specific files. that your app can re-create easily. | 1.0.0 | | **`External`** | 'EXTERNAL' | The external directory. On iOS it will use the Documents directory. On Android it's the directory on the primary shared/external storage device where the application can place persistent files it owns. These files are internal to the applications, and not typically visible to the user as media. Files will be deleted when the application is uninstalled. | 1.0.0 | | **`ExternalStorage`** | 'EXTERNAL_STORAGE' | The external storage directory. On iOS it will use the Documents directory. On Android it's the primary shared/external storage directory. It's not accesible on Android 10 unless the app enables legacy External Storage by adding `android:requestLegacyExternalStorage="true"` in the `application` tag in the `AndroidManifest.xml`. It's not accesible on Android 11 or newer. | 1.0.0 | +| **`PortableStorage`** | 'PORTABLE_STORAGE' | SDCARD when configured as Portable Storage Points to application private storage on the SD Card when configured as Portable Storage. On iOS it will use the Documents directory | 5.0.5 | + #### Encoding diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java index 528e23d2a..e139b0af3 100644 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java +++ b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/Filesystem.java @@ -2,8 +2,10 @@ import android.content.Context; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.util.Base64; +import androidx.core.os.EnvironmentCompat; import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException; import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException; import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException; @@ -26,6 +28,8 @@ import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import org.json.JSONException; @@ -212,10 +216,75 @@ public File getDirectory(String directory) { return c.getExternalFilesDir(null); case "EXTERNAL_STORAGE": return Environment.getExternalStorageDirectory(); + case "PORTABLE_STORAGE": + return this.getPortableStorage(); } return null; } + public boolean isPortableStorageAvailable() { + String[] storageDirectories = getFilteredStorageDirectories(); + return storageDirectories.length > 0; + } + + private File getPortableStorage() { + String[] storageDirectories = getFilteredStorageDirectories(); + + if (storageDirectories.length == 0) { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + } else { + return new File(storageDirectories[0]); + } + } + + private String[] getFilteredStorageDirectories() { + List results = new ArrayList(); + + Context c = this.context; + + File[] externalDirs = c.getExternalFilesDirs(null); + + // Get a list of external file systems and filter for removable storage only + for (File file : externalDirs) { + if (file == null) { + continue; + } + String applicationPath = file.getPath(); + + boolean addPath = false; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + addPath = Environment.isExternalStorageRemovable(file); + } else { + addPath = Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(file)); + } + + if (addPath) { + results.add(applicationPath); + } + } + + //Remove paths which may not be external SDCard, like OTG + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + for (int i = 0; i < results.size(); i++) { + if (!results.get(i).toLowerCase().matches(".*[0-9a-f]{4}[-][0-9a-f]{4}.*")) { + results.remove(i--); + } + } + } else { + for (int i = 0; i < results.size(); i++) { + if (!results.get(i).toLowerCase().contains("ext") && !results.get(i).toLowerCase().contains("sdcard")) { + results.remove(i--); + } + } + } + + String[] storageDirectories = new String[results.size()]; + for (int i = 0; i < results.size(); ++i) storageDirectories[i] = results.get(i); + + return storageDirectories; + } + public File getFileObject(String path, String directory) { if (directory == null) { Uri u = Uri.parse(path); diff --git a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java index 95e979077..7f3035c37 100644 --- a/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java +++ b/filesystem/android/src/main/java/com/capacitorjs/plugins/filesystem/FilesystemPlugin.java @@ -48,6 +48,13 @@ public void load() { private static final String PERMISSION_DENIED_ERROR = "Unable to do file operation, user denied permission request"; + @PluginMethod + public void isPortableStorageAvailable(PluginCall call) { + JSObject ret = new JSObject(); + ret.put("available", implementation.isPortableStorageAvailable()); + call.resolve(ret); + } + @PluginMethod public void readFile(PluginCall call) { String path = call.getString("path"); diff --git a/filesystem/src/definitions.ts b/filesystem/src/definitions.ts index d13af5293..caaf7b261 100644 --- a/filesystem/src/definitions.ts +++ b/filesystem/src/definitions.ts @@ -77,6 +77,16 @@ export enum Directory { * @since 1.0.0 */ ExternalStorage = 'EXTERNAL_STORAGE', + + /** + * SDCARD when configured as Portable Storage + * Points to application private storage on the SD Card when configured as + * Portable Storage. + * On iOS it will use the Documents directory + * + * @since 5.0.5 + */ + PortableStorage = 'PORTABLE_STORAGE', } export enum Encoding { @@ -482,6 +492,16 @@ export interface CopyResult { uri: string; } +export interface isPortableStorageAvailableResult { + /** + * Is portable storage available (SD Card) + * Only available on Android + * + * @since 5.0.5 + */ + available: boolean; +} + export interface DownloadFileOptions extends HttpOptions { /** * The path the downloaded file should be moved to. @@ -529,6 +549,7 @@ export interface DownloadFileResult { */ blob?: Blob; } + export interface ProgressStatus { /** * The url of the file being downloaded. @@ -635,6 +656,14 @@ export interface FilesystemPlugin { */ copy(options: CopyOptions): Promise; + /** + * Check if portable storage is available (SD Card for Android) + * Only available on Android + * + * @since 5.0.5 + */ + isPortableStorageAvailable(): Promise; + /** * Check read/write permissions. * Required on Android, only when using `Directory.Documents` or diff --git a/filesystem/src/web.ts b/filesystem/src/web.ts index 4712fb507..41078b9cf 100644 --- a/filesystem/src/web.ts +++ b/filesystem/src/web.ts @@ -21,6 +21,7 @@ import type { WriteFileOptions, WriteFileResult, Directory, + isPortableStorageAvailableResult, DownloadFileOptions, DownloadFileResult, ProgressStatus, @@ -473,6 +474,10 @@ export class FilesystemWeb extends WebPlugin implements FilesystemPlugin { return { publicStorage: 'granted' }; } + async isPortableStorageAvailable(): Promise { + return { available: false }; + } + /** * Function that can perform a copy or a rename * @param options the options for the rename operation