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 9e9909e8570..5a4b50906ee 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -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; @@ -146,6 +147,10 @@ public class DownloadDialog extends DialogFragment registerForActivityResult( new StartActivityForResult(), this::requestDownloadPickVideoFolderResult); + private final ActivityResultLauncher requestStorageSettingsLauncher = + registerForActivityResult( + new StartActivityForResult(), this::handleStorageSettingsResult); + /*////////////////////////////////////////////////////////////////////////// // Instance creation @@ -564,6 +569,10 @@ private void requestDownloadPickFolderResult(@NonNull final ActivityResult resul } } + private void handleStorageSettingsResult(@NonNull final ActivityResult result) { + // Handle result if needed. In most cases, no action is needed. + } + /*////////////////////////////////////////////////////////////////////////// // Listeners @@ -783,6 +792,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 @@ -794,6 +804,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"; @@ -806,6 +817,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(); @@ -815,6 +827,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; } @@ -870,6 +883,23 @@ 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; + freeSpace = mainStorage.getFreeMemory(); + if (freeSpace <= size) { + Toast.makeText(context, getString(R. + string.error_insufficient_storage), Toast.LENGTH_SHORT).show(); + // move the user to storage setting tab + final Intent storageSettingsIntent = new Intent(Settings. + ACTION_INTERNAL_STORAGE_SETTINGS); + NoFileManagerSafeGuard.launchSafe(requestStorageSettingsLauncher, + storageSettingsIntent, TAG, + context); + return; + } + } + // check for existing file with the same name checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp, mimeTmp); 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 74fc74c76dd..0fe2e04082a 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 @@ -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; @@ -23,6 +27,7 @@ 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; @@ -30,6 +35,8 @@ 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 @@ -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 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. 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 3cfa22bd9f6..c75269757a1 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -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; @@ -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 { @@ -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) {