NewPipe/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java

285 lines
11 KiB
Java

package org.schabi.newpipe.settings;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat;
import com.nononsenseapps.filepicker.Utils;
import org.schabi.newpipe.R;
import org.schabi.newpipe.streams.io.StoredDirectoryHelper;
import org.schabi.newpipe.util.FilePickerActivityHelper;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
public class DownloadSettingsFragment extends BasePreferenceFragment {
public static final boolean IGNORE_RELEASE_ON_OLD_PATH = true;
private String downloadPathVideoPreference;
private String downloadPathAudioPreference;
private String storageUseSafPreference;
private Preference prefPathVideo;
private Preference prefPathAudio;
private Preference prefStorageAsk;
private Context ctx;
private final ActivityResultLauncher<Intent> requestDownloadVideoPathLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadVideoPathResult);
private final ActivityResultLauncher<Intent> requestDownloadAudioPathLauncher =
registerForActivityResult(
new StartActivityForResult(), this::requestDownloadAudioPathResult);
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.download_settings);
downloadPathVideoPreference = getString(R.string.download_path_video_key);
downloadPathAudioPreference = getString(R.string.download_path_audio_key);
storageUseSafPreference = getString(R.string.storage_use_saf);
final String downloadStorageAsk = getString(R.string.downloads_storage_ask);
prefPathVideo = findPreference(downloadPathVideoPreference);
prefPathAudio = findPreference(downloadPathAudioPreference);
prefStorageAsk = findPreference(downloadStorageAsk);
final SwitchPreferenceCompat prefUseSaf = findPreference(storageUseSafPreference);
prefUseSaf.setDefaultValue(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP);
prefUseSaf.setChecked(NewPipeSettings.useStorageAccessFramework(ctx));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
prefUseSaf.setEnabled(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
prefUseSaf.setSummary(R.string.downloads_storage_use_saf_summary_api_29);
} else {
prefUseSaf.setSummary(R.string.downloads_storage_use_saf_summary_api_19);
}
prefStorageAsk.setSummary(R.string.downloads_storage_ask_summary_no_saf_notice);
}
updatePreferencesSummary();
updatePathPickers(!defaultPreferences.getBoolean(downloadStorageAsk, false));
if (hasInvalidPath(downloadPathVideoPreference)
|| hasInvalidPath(downloadPathAudioPreference)) {
updatePreferencesSummary();
}
prefStorageAsk.setOnPreferenceChangeListener((preference, value) -> {
updatePathPickers(!(boolean) value);
return true;
});
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
ctx = context;
}
@Override
public void onDetach() {
super.onDetach();
ctx = null;
prefStorageAsk.setOnPreferenceChangeListener(null);
}
private void updatePreferencesSummary() {
showPathInSummary(downloadPathVideoPreference, R.string.download_path_summary,
prefPathVideo);
showPathInSummary(downloadPathAudioPreference, R.string.download_path_audio_summary,
prefPathAudio);
}
private void showPathInSummary(final String prefKey, @StringRes final int defaultString,
final Preference target) {
String rawUri = defaultPreferences.getString(prefKey, null);
if (rawUri == null || rawUri.isEmpty()) {
target.setSummary(getString(defaultString));
return;
}
if (rawUri.charAt(0) == File.separatorChar) {
target.setSummary(rawUri);
return;
}
if (rawUri.startsWith(ContentResolver.SCHEME_FILE)) {
target.setSummary(new File(URI.create(rawUri)).getPath());
return;
}
try {
rawUri = URLDecoder.decode(rawUri, StandardCharsets.UTF_8.name());
} catch (final UnsupportedEncodingException e) {
// nothing to do
}
target.setSummary(rawUri);
}
private boolean isFileUri(final String path) {
return path.charAt(0) == File.separatorChar || path.startsWith(ContentResolver.SCHEME_FILE);
}
private boolean hasInvalidPath(final String prefKey) {
final String value = defaultPreferences.getString(prefKey, null);
return value == null || value.isEmpty();
}
private void updatePathPickers(final boolean enabled) {
prefPathVideo.setEnabled(enabled);
prefPathAudio.setEnabled(enabled);
}
// FIXME: after releasing the old path, all downloads created on the folder becomes inaccessible
private void forgetSAFTree(final Context context, final String oldPath) {
if (IGNORE_RELEASE_ON_OLD_PATH) {
return;
}
if (oldPath == null || oldPath.isEmpty() || isFileUri(oldPath)) {
return;
}
try {
final Uri uri = Uri.parse(oldPath);
context.getContentResolver()
.releasePersistableUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS);
context.revokeUriPermission(uri, StoredDirectoryHelper.PERMISSION_FLAGS);
Log.i(TAG, "Revoke old path permissions success on " + oldPath);
} catch (final Exception err) {
Log.e(TAG, "Error revoking old path permissions on " + oldPath, err);
}
}
private void showMessageDialog(@StringRes final int title, @StringRes final int message) {
final AlertDialog.Builder msg = new AlertDialog.Builder(ctx);
msg.setTitle(title);
msg.setMessage(message);
msg.setPositiveButton(getString(R.string.ok), null);
msg.show();
}
@Override
public boolean onPreferenceTreeClick(final Preference preference) {
if (DEBUG) {
Log.d(TAG, "onPreferenceTreeClick() called with: "
+ "preference = [" + preference + "]");
}
final String key = preference.getKey();
if (key.equals(storageUseSafPreference)) {
if (!NewPipeSettings.useStorageAccessFramework(ctx)) {
NewPipeSettings.saveDefaultVideoDownloadDirectory(ctx);
NewPipeSettings.saveDefaultAudioDownloadDirectory(ctx);
} else {
defaultPreferences.edit().putString(downloadPathVideoPreference, null)
.putString(downloadPathAudioPreference, null).apply();
}
updatePreferencesSummary();
return true;
} else if (key.equals(downloadPathVideoPreference)) {
launchDirectoryPicker(requestDownloadVideoPathLauncher);
} else if (key.equals(downloadPathAudioPreference)) {
launchDirectoryPicker(requestDownloadAudioPathLauncher);
} else {
return super.onPreferenceTreeClick(preference);
}
return true;
}
private void launchDirectoryPicker(final ActivityResultLauncher<Intent> launcher) {
launcher.launch(StoredDirectoryHelper.getPicker(ctx));
}
private void requestDownloadVideoPathResult(final ActivityResult result) {
requestDownloadPathResult(result, downloadPathVideoPreference);
}
private void requestDownloadAudioPathResult(final ActivityResult result) {
requestDownloadPathResult(result, downloadPathAudioPreference);
}
private void requestDownloadPathResult(final ActivityResult result, final String key) {
assureCorrectAppLanguage(getContext());
if (result.getResultCode() != Activity.RESULT_OK) {
return;
}
Uri uri = null;
if (result.getData() != null) {
uri = result.getData().getData();
}
if (uri == null) {
showMessageDialog(R.string.general_error, R.string.invalid_directory);
return;
}
// revoke permissions on the old save path (required for SAF only)
final Context context = requireContext();
forgetSAFTree(context, defaultPreferences.getString(key, ""));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
&& !FilePickerActivityHelper.isOwnFileUri(context, uri)) {
// steps to acquire the selected path:
// 1. acquire permissions on the new save path
// 2. save the new path, if step(2) was successful
try {
context.grantUriPermission(context.getPackageName(), uri,
StoredDirectoryHelper.PERMISSION_FLAGS);
final StoredDirectoryHelper mainStorage
= new StoredDirectoryHelper(context, uri, null);
Log.i(TAG, "Acquiring tree success from " + uri.toString());
if (!mainStorage.canWrite()) {
throw new IOException("No write permissions on " + uri.toString());
}
} catch (final IOException err) {
Log.e(TAG, "Error acquiring tree from " + uri.toString(), err);
showMessageDialog(R.string.general_error, R.string.no_available_dir);
return;
}
} else {
final File target = Utils.getFileForUri(uri);
if (!target.canWrite()) {
showMessageDialog(R.string.download_to_sdcard_error_title,
R.string.download_to_sdcard_error_message);
return;
}
uri = Uri.fromFile(target);
}
defaultPreferences.edit().putString(key, uri.toString()).apply();
updatePreferencesSummary();
}
}