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+
This commit is contained in:
CloudyRowly 2024-03-21 19:18:55 +11:00 committed by GitHub
parent 5bdb6f18d6
commit 2e318b8b03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 4 deletions

View File

@ -16,6 +16,7 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -147,7 +148,6 @@ public class DownloadDialog extends DialogFragment
registerForActivityResult( registerForActivityResult(
new StartActivityForResult(), this::requestDownloadPickVideoFolderResult); new StartActivityForResult(), this::requestDownloadPickVideoFolderResult);
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Instance creation // Instance creation
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -565,7 +565,6 @@ public class DownloadDialog extends DialogFragment
} }
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Listeners // Listeners
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@ -784,6 +783,7 @@ public class DownloadDialog extends DialogFragment
final StoredDirectoryHelper mainStorage; final StoredDirectoryHelper mainStorage;
final MediaFormat format; final MediaFormat format;
final String selectedMediaType; final String selectedMediaType;
final long size;
// first, build the filename and get the output folder (if possible) // first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic // later, run a very very very large file checking logic
@ -795,6 +795,7 @@ public class DownloadDialog extends DialogFragment
selectedMediaType = getString(R.string.last_download_type_audio_key); selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio; mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat(); format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
if (format == MediaFormat.WEBMA_OPUS) { if (format == MediaFormat.WEBMA_OPUS) {
mimeTmp = "audio/ogg"; mimeTmp = "audio/ogg";
filenameTmp += "opus"; filenameTmp += "opus";
@ -807,6 +808,7 @@ public class DownloadDialog extends DialogFragment
selectedMediaType = getString(R.string.last_download_type_video_key); selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo; mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
if (format != null) { if (format != null) {
mimeTmp = format.mimeType; mimeTmp = format.mimeType;
filenameTmp += format.getSuffix(); filenameTmp += format.getSuffix();
@ -816,6 +818,7 @@ public class DownloadDialog extends DialogFragment
selectedMediaType = getString(R.string.last_download_type_subtitle_key); selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat(); format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
if (format != null) { if (format != null) {
mimeTmp = format.mimeType; mimeTmp = format.mimeType;
} }
@ -871,6 +874,22 @@ public class DownloadDialog extends DialogFragment
return; 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 // check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp, checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
mimeTmp); mimeTmp);

View File

@ -5,11 +5,15 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
@ -23,6 +27,7 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -30,6 +35,8 @@ import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
import us.shandian.giga.util.Utility;
public class StoredDirectoryHelper { public class StoredDirectoryHelper {
private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -168,6 +175,44 @@ public class StoredDirectoryHelper {
return docTree == null; 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 * Only using Java I/O. Creates the directory named by this abstract pathname, including any
* necessary but nonexistent parent directories. * necessary but nonexistent parent directories.

View File

@ -2,6 +2,8 @@ package us.shandian.giga.util;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log; import android.util.Log;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
@ -26,10 +28,8 @@ import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.util.Locale; import java.util.Locale;
import java.util.Random;
import okio.ByteString; import okio.ByteString;
import us.shandian.giga.get.DownloadMission;
public class Utility { public class Utility {
@ -40,6 +40,20 @@ public class Utility {
UNKNOWN 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) { public static String formatBytes(long bytes) {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
if (bytes < 1024) { if (bytes < 1024) {