Skip to content

Commit 2e318b8

Browse files
authored
Added "free memory" check before downloading [Android N / API 24+] (#10505)
Added "free space" check before downloading eliminating bugs related to out-of-memory on Android N / API 24+
1 parent 5bdb6f1 commit 2e318b8

3 files changed

Lines changed: 82 additions & 4 deletions

File tree

app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import android.os.Bundle;
1717
import android.os.Environment;
1818
import android.os.IBinder;
19+
import android.provider.Settings;
1920
import android.util.Log;
2021
import android.view.LayoutInflater;
2122
import android.view.View;
@@ -147,7 +148,6 @@ public class DownloadDialog extends DialogFragment
147148
registerForActivityResult(
148149
new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);
149150

150-
151151
/*//////////////////////////////////////////////////////////////////////////
152152
// Instance creation
153153
//////////////////////////////////////////////////////////////////////////*/
@@ -565,7 +565,6 @@ private void requestDownloadPickFolderResult(@NonNull final ActivityResult resul
565565
}
566566
}
567567

568-
569568
/*//////////////////////////////////////////////////////////////////////////
570569
// Listeners
571570
//////////////////////////////////////////////////////////////////////////*/
@@ -784,6 +783,7 @@ private void prepareSelectedDownload() {
784783
final StoredDirectoryHelper mainStorage;
785784
final MediaFormat format;
786785
final String selectedMediaType;
786+
final long size;
787787

788788
// first, build the filename and get the output folder (if possible)
789789
// later, run a very very very large file checking logic
@@ -795,6 +795,7 @@ private void prepareSelectedDownload() {
795795
selectedMediaType = getString(R.string.last_download_type_audio_key);
796796
mainStorage = mainStorageAudio;
797797
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
798+
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
798799
if (format == MediaFormat.WEBMA_OPUS) {
799800
mimeTmp = "audio/ogg";
800801
filenameTmp += "opus";
@@ -807,6 +808,7 @@ private void prepareSelectedDownload() {
807808
selectedMediaType = getString(R.string.last_download_type_video_key);
808809
mainStorage = mainStorageVideo;
809810
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
811+
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
810812
if (format != null) {
811813
mimeTmp = format.mimeType;
812814
filenameTmp += format.getSuffix();
@@ -816,6 +818,7 @@ private void prepareSelectedDownload() {
816818
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
817819
mainStorage = mainStorageVideo; // subtitle & video files go together
818820
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
821+
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
819822
if (format != null) {
820823
mimeTmp = format.mimeType;
821824
}
@@ -871,6 +874,22 @@ private void prepareSelectedDownload() {
871874
return;
872875
}
873876

877+
// Check for free memory space (for api 24 and up)
878+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
879+
final long freeSpace = mainStorage.getFreeMemory();
880+
if (freeSpace <= size) {
881+
Toast.makeText(context, getString(R.
882+
string.error_insufficient_storage), Toast.LENGTH_LONG).show();
883+
// move the user to storage setting tab
884+
final Intent storageSettingsIntent = new Intent(Settings.
885+
ACTION_INTERNAL_STORAGE_SETTINGS);
886+
if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) {
887+
startActivity(storageSettingsIntent);
888+
}
889+
return;
890+
}
891+
}
892+
874893
// check for existing file with the same name
875894
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
876895
mimeTmp);

app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@
55
import android.content.Intent;
66
import android.database.Cursor;
77
import android.net.Uri;
8+
import android.os.Build;
9+
import android.os.storage.StorageManager;
10+
import android.os.storage.StorageVolume;
811
import android.provider.DocumentsContract;
912
import android.util.Log;
1013

1114
import androidx.annotation.NonNull;
1215
import androidx.annotation.Nullable;
16+
import androidx.annotation.RequiresApi;
1317
import androidx.documentfile.provider.DocumentFile;
1418

1519
import org.schabi.newpipe.settings.NewPipeSettings;
@@ -23,13 +27,16 @@
2327
import java.util.ArrayList;
2428
import java.util.Collections;
2529
import java.util.List;
30+
import java.util.UUID;
2631
import java.util.stream.Collectors;
2732
import java.util.stream.Stream;
2833

2934
import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
3035
import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
3136
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
3237

38+
import us.shandian.giga.util.Utility;
39+
3340
public class StoredDirectoryHelper {
3441
private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
3542
public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -168,6 +175,44 @@ public boolean isDirect() {
168175
return docTree == null;
169176
}
170177

178+
/**
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)
181+
*/
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+
}
199+
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+
}
209+
}
210+
}
211+
}
212+
}
213+
return Long.MAX_VALUE;
214+
}
215+
171216
/**
172217
* Only using Java I/O. Creates the directory named by this abstract pathname, including any
173218
* necessary but nonexistent parent directories.

app/src/main/java/us/shandian/giga/util/Utility.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import android.content.Context;
44
import android.os.Build;
5+
import android.os.Environment;
6+
import android.os.StatFs;
57
import android.util.Log;
68

79
import androidx.annotation.ColorInt;
@@ -26,10 +28,8 @@
2628
import java.io.Serializable;
2729
import java.net.HttpURLConnection;
2830
import java.util.Locale;
29-
import java.util.Random;
3031

3132
import okio.ByteString;
32-
import us.shandian.giga.get.DownloadMission;
3333

3434
public class Utility {
3535

@@ -40,6 +40,20 @@ public enum FileType {
4040
UNKNOWN
4141
}
4242

43+
/**
44+
* Get amount of free system's memory.
45+
* @return free memory (bytes)
46+
*/
47+
public static long getSystemFreeMemory() {
48+
try {
49+
final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
50+
return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
51+
} catch (final Exception e) {
52+
// do nothing
53+
}
54+
return -1;
55+
}
56+
4357
public static String formatBytes(long bytes) {
4458
Locale locale = Locale.getDefault();
4559
if (bytes < 1024) {

0 commit comments

Comments
 (0)