Skip to content

Commit

Permalink
Added "free memory" check before downloading [Android N / API 24+] (#…
Browse files Browse the repository at this point in the history
…10505)

Added "free space" check before downloading eliminating bugs related to out-of-memory on Android N / API 24+
  • Loading branch information
CloudyRowly authored Mar 21, 2024
1 parent 5bdb6f1 commit 2e318b8
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
23 changes: 21 additions & 2 deletions app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
Expand Down Expand Up @@ -147,7 +148,6 @@ public class DownloadDialog extends DialogFragment
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);


/*//////////////////////////////////////////////////////////////////////////
// Instance creation
//////////////////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -565,7 +565,6 @@ private void requestDownloadPickFolderResult(@NonNull final ActivityResult resul
}
}


/*//////////////////////////////////////////////////////////////////////////
// Listeners
//////////////////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -784,6 +783,7 @@ private void prepareSelectedDownload() {
final StoredDirectoryHelper mainStorage;
final MediaFormat format;
final String selectedMediaType;
final long size;

// first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic
Expand All @@ -795,6 +795,7 @@ private void prepareSelectedDownload() {
selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
if (format == MediaFormat.WEBMA_OPUS) {
mimeTmp = "audio/ogg";
filenameTmp += "opus";
Expand All @@ -807,6 +808,7 @@ private void prepareSelectedDownload() {
selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
if (format != null) {
mimeTmp = format.mimeType;
filenameTmp += format.getSuffix();
Expand All @@ -816,6 +818,7 @@ private void prepareSelectedDownload() {
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
if (format != null) {
mimeTmp = format.mimeType;
}
Expand Down Expand Up @@ -871,6 +874,22 @@ private void prepareSelectedDownload() {
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 existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
mimeTmp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
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.provider.DocumentsContract;
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;
Expand All @@ -23,13 +27,16 @@
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
Expand Down Expand Up @@ -168,6 +175,44 @@ public boolean isDirect() {
return docTree == null;
}

/**
* Get free memory of the storage partition (root of the directory).
* @return amount of free memory in the volume of current directory (bytes)
*/
@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<StorageVolume> volumes = storageManager.getStorageVolumes();

final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
if (split.length > 0) {
final String volumeId = split[0];

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
}
}
}
}
}
return Long.MAX_VALUE;
}

/**
* Only using Java I/O. Creates the directory named by this abstract pathname, including any
* necessary but nonexistent parent directories.
Expand Down
18 changes: 16 additions & 2 deletions app/src/main/java/us/shandian/giga/util/Utility.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;

import androidx.annotation.ColorInt;
Expand All @@ -26,10 +28,8 @@
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.util.Locale;
import java.util.Random;

import okio.ByteString;
import us.shandian.giga.get.DownloadMission;

public class Utility {

Expand All @@ -40,6 +40,20 @@ public enum FileType {
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) {
Expand Down

0 comments on commit 2e318b8

Please sign in to comment.