11package org .schabi .newpipe .streams .io ;
22
3+ import static android .provider .DocumentsContract .Document .COLUMN_DISPLAY_NAME ;
4+ import static android .provider .DocumentsContract .Root .COLUMN_DOCUMENT_ID ;
5+ import static org .schabi .newpipe .extractor .utils .Utils .isNullOrEmpty ;
6+
37import android .content .ContentResolver ;
48import android .content .Context ;
59import android .content .Intent ;
610import android .database .Cursor ;
711import android .net .Uri ;
8- import android .os .Build ;
9- import android .os .storage .StorageManager ;
10- import android .os .storage .StorageVolume ;
12+ import android .os .ParcelFileDescriptor ;
1113import android .provider .DocumentsContract ;
14+ import android .system .Os ;
15+ import android .system .StructStatVfs ;
1216import android .util .Log ;
1317
1418import androidx .annotation .NonNull ;
1519import androidx .annotation .Nullable ;
16- import androidx .annotation .RequiresApi ;
1720import androidx .documentfile .provider .DocumentFile ;
1821
1922import org .schabi .newpipe .settings .NewPipeSettings ;
2023import org .schabi .newpipe .util .FilePickerActivityHelper ;
2124
25+ import java .io .FileDescriptor ;
2226import java .io .IOException ;
2327import java .net .URI ;
2428import java .nio .file .Files ;
2731import java .util .ArrayList ;
2832import java .util .Collections ;
2933import java .util .List ;
30- import java .util .UUID ;
3134import java .util .stream .Collectors ;
3235import java .util .stream .Stream ;
3336
34- import static android .provider .DocumentsContract .Document .COLUMN_DISPLAY_NAME ;
35- import static android .provider .DocumentsContract .Root .COLUMN_DOCUMENT_ID ;
36- import static org .schabi .newpipe .extractor .utils .Utils .isNullOrEmpty ;
37-
38- import us .shandian .giga .util .Utility ;
39-
4037public class StoredDirectoryHelper {
4138 private static final String TAG = StoredDirectoryHelper .class .getSimpleName ();
4239 public static final int PERMISSION_FLAGS = Intent .FLAG_GRANT_READ_URI_PERMISSION
@@ -45,6 +42,10 @@ public class StoredDirectoryHelper {
4542 private Path ioTree ;
4643 private DocumentFile docTree ;
4744
45+ /**
46+ * Context is `null` for non-SAF files, i.e. files that use `ioTree`.
47+ */
48+ @ Nullable
4849 private Context context ;
4950
5051 private final String tag ;
@@ -176,41 +177,43 @@ public boolean isDirect() {
176177 }
177178
178179 /**
179- * Get free memory of the storage partition (root of the directory).
180- * @return amount of free memory in the volume of current directory (bytes)
180+ * Get free memory of the storage partition this file belongs to (root of the directory).
181+ * See <a href="https://stackoverflow.com/q/31171838">StackOverflow</a> and
182+ * <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html">
183+ * {@code statvfs()} and {@code fstatvfs()} docs</a>
184+ *
185+ * @return amount of free memory in the volume of current directory (bytes), or {@link
186+ * Long#MAX_VALUE} if an error occurred
181187 */
182- @ RequiresApi (api = Build .VERSION_CODES .N ) // Necessary for `getStorageVolume()`
183- public long getFreeMemory () {
184- final Uri uri = getUri ();
185- final StorageManager storageManager = (StorageManager ) context .
186- getSystemService (Context .STORAGE_SERVICE );
187- final List <StorageVolume > volumes = storageManager .getStorageVolumes ();
188-
189- final String docId = DocumentsContract .getDocumentId (uri );
190- final String [] split = docId .split (":" );
191- if (split .length > 0 ) {
192- final String volumeId = split [0 ];
193-
194- for (final StorageVolume volume : volumes ) {
195- // if the volume is an internal system volume
196- if (volume .isPrimary () && volumeId .equalsIgnoreCase ("primary" )) {
197- return Utility .getSystemFreeMemory ();
198- }
188+ public long getFreeStorageSpace () {
189+ try {
190+ final StructStatVfs stat ;
191+
192+ if (ioTree != null ) {
193+ // non-SAF file, use statvfs with the path directly (also, `context` would be null
194+ // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
195+ stat = Os .statvfs (ioTree .toString ());
199196
200- // if the volume is a removable volume (normally an SD card)
201- if (volume .isRemovable () && !volume .isPrimary ()) {
202- if (android .os .Build .VERSION .SDK_INT >= android .os .Build .VERSION_CODES .O ) {
203- try {
204- final String sdCardUUID = volume .getUuid ();
205- return storageManager .getAllocatableBytes (UUID .fromString (sdCardUUID ));
206- } catch (final Exception e ) {
207- // do nothing
208- }
197+ } else {
198+ // SAF file, we can't get a path directly, so obtain a file descriptor first
199+ // and then use fstatvfs with the file descriptor
200+ try (ParcelFileDescriptor parcelFileDescriptor =
201+ context .getContentResolver ().openFileDescriptor (getUri (), "r" )) {
202+ if (parcelFileDescriptor == null ) {
203+ return Long .MAX_VALUE ;
209204 }
205+ final FileDescriptor fileDescriptor = parcelFileDescriptor .getFileDescriptor ();
206+ stat = Os .fstatvfs (fileDescriptor );
210207 }
211208 }
209+
210+ // this is the same formula used inside the FsStat class
211+ return stat .f_bavail * stat .f_frsize ;
212+ } catch (final Throwable e ) {
213+ // ignore any error
214+ Log .e (TAG , "Could not get free storage space" , e );
215+ return Long .MAX_VALUE ;
212216 }
213- return Long .MAX_VALUE ;
214217 }
215218
216219 /**
0 commit comments