diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index bbdb46292..db2066b27 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -859,20 +859,19 @@ public class DownloadDialog extends DialogFragment
return;
}
- // Check for free memory space (for api 24 and up)
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
- final long freeSpace = mainStorage.getFreeMemory();
- if (freeSpace <= size) {
- Toast.makeText(context, getString(R.
- string.error_insufficient_storage), Toast.LENGTH_LONG).show();
- // move the user to storage setting tab
- final Intent storageSettingsIntent = new Intent(Settings.
- ACTION_INTERNAL_STORAGE_SETTINGS);
- if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) {
- startActivity(storageSettingsIntent);
- }
- return;
+ // Check for free storage space
+ final long freeSpace = mainStorage.getFreeStorageSpace();
+ if (freeSpace <= size) {
+ Toast.makeText(context, getString(R.
+ string.error_insufficient_storage), Toast.LENGTH_LONG).show();
+ // move the user to storage setting tab
+ final Intent storageSettingsIntent = new Intent(Settings.
+ ACTION_INTERNAL_STORAGE_SETTINGS);
+ if (storageSettingsIntent.resolveActivity(context.getPackageManager())
+ != null) {
+ startActivity(storageSettingsIntent);
}
+ return;
}
// check for existing file with the same name
diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
index 0fe2e0408..8dd819293 100644
--- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java
@@ -1,24 +1,29 @@
package org.schabi.newpipe.streams.io;
+import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
+import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
+import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
+import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStatVfs;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.documentfile.provider.DocumentFile;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilePickerActivityHelper;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
@@ -27,16 +32,9 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
-import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
-import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
-
-import us.shandian.giga.util.Utility;
-
public class StoredDirectoryHelper {
private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -45,6 +43,7 @@ public class StoredDirectoryHelper {
private Path ioTree;
private DocumentFile docTree;
+ // will be `null` for non-SAF files, i.e. files that use `ioTree`
private Context context;
private final String tag;
@@ -176,41 +175,41 @@ public class StoredDirectoryHelper {
}
/**
- * Get free memory of the storage partition (root of the directory).
- * @return amount of free memory in the volume of current directory (bytes)
+ * Get free memory of the storage partition this file belongs to (root of the directory).
+ * See StackOverflow and
+ *
+ * {@code statvfs()} and {@code fstatvfs()} docs
+ *
+ * @return amount of free memory in the volume of current directory (bytes), or {@link
+ * Long#MAX_VALUE} if an error occurred
*/
- @RequiresApi(api = Build.VERSION_CODES.N) // Necessary for `getStorageVolume()`
- public long getFreeMemory() {
- final Uri uri = getUri();
- final StorageManager storageManager = (StorageManager) context.
- getSystemService(Context.STORAGE_SERVICE);
- final List volumes = storageManager.getStorageVolumes();
+ public long getFreeStorageSpace() {
+ try {
+ final StructStatVfs stat;
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- if (split.length > 0) {
- final String volumeId = split[0];
+ if (ioTree != null) {
+ // non-SAF file, use statvfs with the path directly (also, `context` would be null
+ // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
+ stat = Os.statvfs(ioTree.toString());
- for (final StorageVolume volume : volumes) {
- // if the volume is an internal system volume
- if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) {
- return Utility.getSystemFreeMemory();
- }
-
- // if the volume is a removable volume (normally an SD card)
- if (volume.isRemovable() && !volume.isPrimary()) {
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
- try {
- final String sdCardUUID = volume.getUuid();
- return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID));
- } catch (final Exception e) {
- // do nothing
- }
+ } else {
+ // SAF file, we can't get a path directly, so obtain a file descriptor first
+ // and then use fstatvfs with the file descriptor
+ try (ParcelFileDescriptor parcelFileDescriptor =
+ context.getContentResolver().openFileDescriptor(getUri(), "r")) {
+ if (parcelFileDescriptor == null) {
+ return Long.MAX_VALUE;
}
+ final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
+ stat = Os.fstatvfs(fileDescriptor);
}
}
+
+ // this is the same formula used inside the FsStat class
+ return stat.f_bavail * stat.f_frsize;
+ } catch (final IOException | ErrnoException e) {
+ return Long.MAX_VALUE;
}
- return Long.MAX_VALUE;
}
/**
diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java
index c75269757..86a08c57f 100644
--- a/app/src/main/java/us/shandian/giga/util/Utility.java
+++ b/app/src/main/java/us/shandian/giga/util/Utility.java
@@ -40,20 +40,6 @@ public class Utility {
UNKNOWN
}
- /**
- * Get amount of free system's memory.
- * @return free memory (bytes)
- */
- public static long getSystemFreeMemory() {
- try {
- final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
- return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
- } catch (final Exception e) {
- // do nothing
- }
- return -1;
- }
-
public static String formatBytes(long bytes) {
Locale locale = Locale.getDefault();
if (bytes < 1024) {