Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SAF properly #5415

Merged
merged 15 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:theme="@style/OpeningTheme"
android:resizeableActivity="true"
tools:ignore="AllowBackup">
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/org/schabi/newpipe/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.ServiceHelper;
import org.schabi.newpipe.util.StateSaver;
Expand Down Expand Up @@ -91,7 +91,7 @@ public void onCreate() {
app = this;

// Initialize settings first because others inits can use its values
SettingsActivity.initSettings(this);
NewPipeSettings.initSettings(this);

NewPipe.init(getDownloader(),
Localization.getPreferredLocalization(this),
Expand Down
150 changes: 97 additions & 53 deletions app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.streams.io.StoredFileHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import org.schabi.newpipe.util.FilenameUtils;
import org.schabi.newpipe.util.ListHelper;
Expand All @@ -68,8 +70,6 @@
import icepick.State;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import us.shandian.giga.get.MissionRecoveryInfo;
import us.shandian.giga.io.StoredDirectoryHelper;
import us.shandian.giga.io.StoredFileHelper;
import us.shandian.giga.postprocessing.Postprocessing;
import us.shandian.giga.service.DownloadManager;
import us.shandian.giga.service.DownloadManagerService;
Expand All @@ -83,6 +83,8 @@ public class DownloadDialog extends DialogFragment
private static final String TAG = "DialogFragment";
private static final boolean DEBUG = MainActivity.DEBUG;
private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230;
private static final int REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER = 0x789E;
private static final int REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER = 0x789F;

@State
StreamInfo currentInfo;
Expand Down Expand Up @@ -116,6 +118,10 @@ public class DownloadDialog extends DialogFragment

private SharedPreferences prefs;

// Variables for file name and MIME type when picking new folder because it's not set yet
private String filenameTmp;
private String mimeTmp;

public static DownloadDialog newInstance(final StreamInfo info) {
final DownloadDialog dialog = new DownloadDialog();
dialog.setInfo(info);
Expand Down Expand Up @@ -153,10 +159,6 @@ public void setVideoStreams(final List<VideoStream> videoStreams) {
setVideoStreams(new StreamSizeWrapper<>(videoStreams, getContext()));
}

/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/

public void setVideoStreams(final StreamSizeWrapper<VideoStream> wvs) {
this.wrappedVideoStreams = wvs;
}
Expand Down Expand Up @@ -374,12 +376,16 @@ public void onSaveInstanceState(@NonNull final Bundle outState) {
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) {
if (data.getData() == null) {
showFailedDialog(R.string.general_error);
return;
}
if (resultCode != Activity.RESULT_OK) {
return;
}

if (data.getData() == null) {
showFailedDialog(R.string.general_error);
return;
}

if (requestCode == REQUEST_DOWNLOAD_SAVE_AS) {
if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) {
final File file = Utils.getFileForUri(data.getData());
checkSelectedDownload(null, Uri.fromFile(file), file.getName(),
Expand All @@ -396,6 +402,37 @@ public void onActivityResult(final int requestCode, final int resultCode, final
// check if the selected file was previously used
checkSelectedDownload(null, data.getData(), docFile.getName(),
docFile.getType());
} else if (requestCode == REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER
|| requestCode == REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER) {
Uri uri = data.getData();
if (FilePickerActivityHelper.isOwnFileUri(context, uri)) {
uri = Uri.fromFile(Utils.getFileForUri(uri));
} else {
context.grantUriPermission(context.getPackageName(), uri,
StoredDirectoryHelper.PERMISSION_FLAGS);
}

final String key;
final String tag;
if (requestCode == REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER) {
key = getString(R.string.download_path_audio_key);
tag = DownloadManager.TAG_AUDIO;
} else {
key = getString(R.string.download_path_video_key);
tag = DownloadManager.TAG_VIDEO;
}

PreferenceManager.getDefaultSharedPreferences(context).edit()
.putString(key, uri.toString()).apply();

try {
final StoredDirectoryHelper mainStorage
= new StoredDirectoryHelper(context, uri, tag);
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp),
filenameTmp, mimeTmp);
} catch (final IOException e) {
showFailedDialog(R.string.general_error);
}
}
}

Expand Down Expand Up @@ -603,84 +640,92 @@ private void showFailedDialog(@StringRes final int msg) {
private void prepareSelectedDownload() {
final StoredDirectoryHelper mainStorage;
final MediaFormat format;
final String mime;
final String selectedMediaType;

// first, build the filename and get the output folder (if possible)
// later, run a very very very large file checking logic

String filename = getNameEditText().concat(".");
filenameTmp = getNameEditText().concat(".");

switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
case R.id.audio_button:
selectedMediaType = getString(R.string.last_download_type_audio_key);
mainStorage = mainStorageAudio;
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
switch (format) {
case WEBMA_OPUS:
mime = "audio/ogg";
filename += "opus";
break;
default:
mime = format.mimeType;
filename += format.suffix;
break;
if (format == MediaFormat.WEBMA_OPUS) {
mimeTmp = "audio/ogg";
filenameTmp += "opus";
} else {
mimeTmp = format.mimeType;
filenameTmp += format.suffix;
}
break;
case R.id.video_button:
selectedMediaType = getString(R.string.last_download_type_video_key);
mainStorage = mainStorageVideo;
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
mime = format.mimeType;
filename += format.suffix;
mimeTmp = format.mimeType;
filenameTmp += format.suffix;
break;
case R.id.subtitle_button:
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
mainStorage = mainStorageVideo; // subtitle & video files go together
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
mime = format.mimeType;
filename += (format == MediaFormat.TTML ? MediaFormat.SRT : format).suffix;
mimeTmp = format.mimeType;
filenameTmp += (format == MediaFormat.TTML ? MediaFormat.SRT : format).suffix;
break;
default:
throw new RuntimeException("No stream selected");
}

if (mainStorage == null || askForSavePath) {
// This part is called if with SAF preferred:
// * older android version running
// * save path not defined (via download settings)
// * the user checked the "ask where to download" option

if (!askForSavePath) {
Toast.makeText(context, getString(R.string.no_available_dir),
Toast.LENGTH_LONG).show();
if (!askForSavePath
&& (mainStorage == null
|| mainStorage.isDirect() == NewPipeSettings.useStorageAccessFramework(context)
|| mainStorage.isInvalidSafStorage())) {
// Pick new download folder if one of:
// - Download folder is not set
// - Download folder uses SAF while SAF is disabled
// - Download folder doesn't use SAF while SAF is enabled
// - Download folder uses SAF but the user manually revoked access to it
Toast.makeText(context, getString(R.string.no_dir_yet),
Toast.LENGTH_LONG).show();

if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
startActivityForResult(StoredDirectoryHelper.getPicker(context),
REQUEST_DOWNLOAD_PICK_AUDIO_FOLDER);
} else {
startActivityForResult(StoredDirectoryHelper.getPicker(context),
REQUEST_DOWNLOAD_PICK_VIDEO_FOLDER);
}

return;
}

if (askForSavePath) {
final Uri initialPath;
if (NewPipeSettings.useStorageAccessFramework(context)) {
StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS,
filename, mime);
initialPath = null;
} else {
File initialSavePath;
final File initialSavePath;
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC);
} else {
initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES);
}

initialSavePath = new File(initialSavePath, filename);
startActivityForResult(FilePickerActivityHelper.chooseFileToSave(context,
initialSavePath.getAbsolutePath()), REQUEST_DOWNLOAD_SAVE_AS);
initialPath = Uri.parse(initialSavePath.getAbsolutePath());
}

startActivityForResult(StoredFileHelper.getNewPicker(context,
filenameTmp, mimeTmp, initialPath), REQUEST_DOWNLOAD_SAVE_AS);

return;
}

// check for existing file with the same name
checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime);
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp, mimeTmp);

// remember the last media type downloaded by the user
prefs.edit()
.putString(getString(R.string.last_used_download_type), selectedMediaType)
prefs.edit().putString(getString(R.string.last_used_download_type), selectedMediaType)
.apply();
}

Expand Down Expand Up @@ -708,15 +753,14 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
return;
}

// check if is our file
// get state of potential mission referring to the same file
final MissionState state = downloadManager.checkForExistingMission(storage);
@StringRes
final int msgBtn;
@StringRes
final int msgBody;
@StringRes final int msgBtn;
@StringRes final int msgBody;

// this switch checks if there is already a mission referring to the same file
switch (state) {
case Finished:
case Finished: // there is already a finished mission
msgBtn = R.string.overwrite;
msgBody = R.string.overwrite_finished_warning;
break;
Expand All @@ -728,7 +772,7 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
msgBtn = R.string.generate_unique_name;
msgBody = R.string.download_already_running;
break;
case None:
case None: // there is no mission referring to the same file
if (mainStorage == null) {
// This part is called if:
// * using SAF on older android version
Expand Down Expand Up @@ -763,7 +807,7 @@ private void checkSelectedDownload(final StoredDirectoryHelper mainStorage,
msgBody = R.string.overwrite_unrelated_warning;
break;
default:
return;
return; // unreachable
}

final AlertDialog.Builder askDialog = new AlertDialog.Builder(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.Menu
Expand All @@ -21,7 +20,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import com.nononsenseapps.filepicker.Utils
import com.xwray.groupie.Group
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Item
Expand Down Expand Up @@ -52,17 +50,15 @@ import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
import org.schabi.newpipe.util.FilePickerActivityHelper
import org.schabi.newpipe.streams.io.StoredFileHelper
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
import org.schabi.newpipe.util.ShareUtils
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
Expand Down Expand Up @@ -188,15 +184,17 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
}

private fun onImportPreviousSelected() {
startActivityForResult(FilePickerActivityHelper.chooseSingleFile(activity), REQUEST_IMPORT_CODE)
startActivityForResult(StoredFileHelper.getPicker(activity), REQUEST_IMPORT_CODE)
}

private fun onExportSelected() {
val date = SimpleDateFormat("yyyyMMddHHmm", Locale.ENGLISH).format(Date())
val exportName = "newpipe_subscriptions_$date.json"
val exportFile = File(Environment.getExternalStorageDirectory(), exportName)

startActivityForResult(FilePickerActivityHelper.chooseFileToSave(activity, exportFile.absolutePath), REQUEST_EXPORT_CODE)
startActivityForResult(
StoredFileHelper.getNewPicker(activity, exportName, "application/json", null),
REQUEST_EXPORT_CODE
)
}

private fun openReorderDialog() {
Expand All @@ -207,23 +205,16 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
super.onActivityResult(requestCode, resultCode, data)
if (data != null && data.data != null && resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_EXPORT_CODE) {
val exportFile = Utils.getFileForUri(data.data!!)
val parentFile = exportFile.parentFile!!
if (!parentFile.canWrite() || !parentFile.canRead()) {
Toast.makeText(activity, R.string.invalid_directory, Toast.LENGTH_SHORT).show()
} else {
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
.putExtra(KEY_FILE_PATH, exportFile.absolutePath)
)
}
activity.startService(
Intent(activity, SubscriptionsExportService::class.java)
.putExtra(SubscriptionsExportService.KEY_FILE_PATH, data.data)
)
} else if (requestCode == REQUEST_IMPORT_CODE) {
val path = Utils.getFileForUri(data.data!!).absolutePath
ImportConfirmationDialog.show(
this,
Intent(activity, SubscriptionsImportService::class.java)
.putExtra(KEY_MODE, PREVIOUS_EXPORT_MODE)
.putExtra(KEY_VALUE, path)
.putExtra(KEY_VALUE, data.data)
)
}
}
Expand Down Expand Up @@ -295,7 +286,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {

private fun showLongTapDialog(selectedItem: ChannelInfoItem) {
val commands = arrayOf(
getString(R.string.share), getString(R.string.open_in_browser),
getString(R.string.share),
getString(R.string.open_in_browser),
getString(R.string.unsubscribe)
)

Expand Down
Loading