From 34b2b9615832a9e1846b6dcfd2b8b6a0e656f162 Mon Sep 17 00:00:00 2001 From: kapodamy Date: Fri, 19 Apr 2019 16:18:19 -0300 Subject: [PATCH] Simplify the storage APIs use * use Java I/O (classic way) on older android versions * use Storage Access Framework on newer android versions (Android Lollipop or later) * both changes have the external SD Card write permission * add option to ask the save path on each download * warn the user if the save paths are not defined, this only happens on the first NewPipe run (Android Lollipop or later) --- .../newpipe/download/DownloadDialog.java | 5 +- .../settings/DownloadSettingsFragment.java | 117 +++++++----------- .../newpipe/settings/NewPipeSettings.java | 19 +-- .../giga/io/StoredDirectoryHelper.java | 5 +- .../giga/service/DownloadManagerService.java | 104 ++++++++-------- app/src/main/res/values-es/strings.xml | 12 +- app/src/main/res/values/settings_keys.xml | 15 +-- app/src/main/res/values/strings.xml | 12 +- app/src/main/res/xml/download_settings.xml | 12 +- 9 files changed, 119 insertions(+), 182 deletions(-) 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 8f4b569cd..f27e7467e 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -212,6 +212,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck mainStorageAudio = mgr.getMainStorageAudio(); mainStorageVideo = mgr.getMainStorageVideo(); downloadManager = mgr.getDownloadManager(); + askForSavePath = mgr.askForSavePath(); okButton.setEnabled(true); @@ -509,6 +510,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck DownloadManager downloadManager = null; ActionMenuItemView okButton = null; Context context; + boolean askForSavePath; private String getNameEditText() { String str = nameEditText.getText().toString().trim(); @@ -567,10 +569,11 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck throw new RuntimeException("No stream selected"); } - if (mainStorage == null) { + 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 as checked the "ask where to download" option StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_PATH_SAF, filename, mime); return; diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index 9fbf7dbea..e671e4d3a 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -9,7 +9,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; import android.support.annotation.StringRes; import android.support.v7.preference.Preference; import android.util.Log; @@ -37,50 +36,40 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { private String DOWNLOAD_PATH_VIDEO_PREFERENCE; private String DOWNLOAD_PATH_AUDIO_PREFERENCE; - private String DOWNLOAD_STORAGE_API; - private String DOWNLOAD_STORAGE_API_DEFAULT; + private String DOWNLOAD_STORAGE_ASK; private Preference prefPathVideo; private Preference prefPathAudio; + private Preference prefStorageAsk; private Context ctx; - private boolean lastAPIJavaIO; - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); DOWNLOAD_PATH_VIDEO_PREFERENCE = getString(R.string.download_path_video_key); DOWNLOAD_PATH_AUDIO_PREFERENCE = getString(R.string.download_path_audio_key); - DOWNLOAD_STORAGE_API = getString(R.string.downloads_storage_api); - DOWNLOAD_STORAGE_API_DEFAULT = getString(R.string.downloads_storage_api_default); + DOWNLOAD_STORAGE_ASK = getString(R.string.downloads_storage_ask); prefPathVideo = findPreference(DOWNLOAD_PATH_VIDEO_PREFERENCE); prefPathAudio = findPreference(DOWNLOAD_PATH_AUDIO_PREFERENCE); - - lastAPIJavaIO = usingJavaIO(); + prefStorageAsk = findPreference(DOWNLOAD_STORAGE_ASK); updatePreferencesSummary(); - updatePathPickers(lastAPIJavaIO); + updatePathPickers(!defaultPreferences.getBoolean(DOWNLOAD_STORAGE_ASK, false)); - findPreference(DOWNLOAD_STORAGE_API).setOnPreferenceChangeListener((preference, value) -> { - boolean javaIO = DOWNLOAD_STORAGE_API_DEFAULT.equals(value); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + prefStorageAsk.setSummary(R.string.downloads_storage_ask_summary); + } - if (javaIO == lastAPIJavaIO) return true; - lastAPIJavaIO = javaIO; + if (hasInvalidPath(DOWNLOAD_PATH_VIDEO_PREFERENCE) || hasInvalidPath(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { + Toast.makeText(ctx, R.string.download_pick_path, Toast.LENGTH_SHORT).show(); + updatePreferencesSummary(); + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - boolean res = forgetPath(DOWNLOAD_PATH_VIDEO_PREFERENCE); - res |= forgetPath(DOWNLOAD_PATH_AUDIO_PREFERENCE); - - if (res) { - Toast.makeText(ctx, R.string.download_pick_path, Toast.LENGTH_SHORT).show(); - updatePreferencesSummary(); - } - } - - updatePathPickers(javaIO); + prefStorageAsk.setOnPreferenceChangeListener((preference, value) -> { + updatePathPickers(!(boolean) value); return true; }); } @@ -100,7 +89,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { public void onDetach() { super.onDetach(); ctx = null; - findPreference(DOWNLOAD_STORAGE_API).setOnPreferenceChangeListener(null); + prefStorageAsk.setOnPreferenceChangeListener(null); } private void updatePreferencesSummary() { @@ -133,34 +122,18 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { target.setSummary(rawUri); } - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - private boolean forgetPath(String prefKey) { - String path = defaultPreferences.getString(prefKey, ""); - if (path == null || path.isEmpty()) return true; - - // forget SAF path if necessary - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - forgetSAFTree(getContext(), path); - - defaultPreferences.edit().putString(prefKey, "").apply(); - - return true; - } - private boolean isFileUri(String path) { return path.charAt(0) == File.separatorChar || path.startsWith(ContentResolver.SCHEME_FILE); } - private void updatePathPickers(boolean useJavaIO) { - boolean enabled = useJavaIO || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - prefPathVideo.setEnabled(enabled); - prefPathAudio.setEnabled(enabled); + private boolean hasInvalidPath(String prefKey) { + String value = defaultPreferences.getString(prefKey, null); + return value == null || value.isEmpty(); } - private boolean usingJavaIO() { - return DOWNLOAD_STORAGE_API_DEFAULT.equals( - defaultPreferences.getString(DOWNLOAD_STORAGE_API, DOWNLOAD_STORAGE_API_DEFAULT) - ); + private void updatePathPickers(boolean enabled) { + prefPathVideo.setEnabled(enabled); + prefPathAudio.setEnabled(enabled); } // FIXME: after releasing the old path, all downloads created on the folder becomes inaccessible @@ -198,33 +171,31 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { } String key = preference.getKey(); + int request; - if (key.equals(DOWNLOAD_PATH_VIDEO_PREFERENCE) || key.equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { - boolean safPick = !usingJavaIO(); - - int request = 0; - if (key.equals(DOWNLOAD_PATH_VIDEO_PREFERENCE)) { - request = REQUEST_DOWNLOAD_VIDEO_PATH; - } else if (key.equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { - request = REQUEST_DOWNLOAD_AUDIO_PATH; - } - - Intent i; - if (safPick && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - .putExtra("android.content.extra.SHOW_ADVANCED", true) - .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | StoredDirectoryHelper.PERMISSION_FLAGS); - } else { - i = new Intent(getActivity(), FilePickerActivityHelper.class) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) - .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) - .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); - } - - startActivityForResult(i, request); + if (key.equals(DOWNLOAD_PATH_VIDEO_PREFERENCE)) { + request = REQUEST_DOWNLOAD_VIDEO_PATH; + } else if (key.equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { + request = REQUEST_DOWNLOAD_AUDIO_PATH; + } else { + return super.onPreferenceTreeClick(preference); } - return super.onPreferenceTreeClick(preference); + Intent i; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + .putExtra("android.content.extra.SHOW_ADVANCED", true) + .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | StoredDirectoryHelper.PERMISSION_FLAGS); + } else { + i = new Intent(getActivity(), FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); + } + + startActivityForResult(i, request); + + return true; } @Override @@ -252,7 +223,7 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { return; } - if (!usingJavaIO() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // steps: // 1. revoke permissions on the old save path // 2. acquire permissions on the new save path diff --git a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java index f153cf23a..f18d90a95 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java +++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java @@ -94,24 +94,7 @@ public class NewPipeSettings { return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName); } - public static void resetDownloadFolders(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - - prefs.edit() - .putString(context.getString(R.string.downloads_storage_api), context.getString(R.string.downloads_storage_api_default)) - .apply(); - - resetDownloadFolder(prefs, context.getString(R.string.download_path_audio_key), Environment.DIRECTORY_MUSIC); - resetDownloadFolder(prefs, context.getString(R.string.download_path_video_key), Environment.DIRECTORY_MOVIES); - } - - private static void resetDownloadFolder(SharedPreferences prefs, String key, String defaultDirectoryName) { - SharedPreferences.Editor spEditor = prefs.edit(); - spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName))); - spEditor.apply(); - } - private static String getNewPipeChildFolderPathForDir(File dir) { - return new File(dir, "NewPipe").getAbsolutePath(); + return new File(dir, "NewPipe").toURI().toString(); } } diff --git a/app/src/main/java/us/shandian/giga/io/StoredDirectoryHelper.java b/app/src/main/java/us/shandian/giga/io/StoredDirectoryHelper.java index eb3c9b817..a65c4dff3 100644 --- a/app/src/main/java/us/shandian/giga/io/StoredDirectoryHelper.java +++ b/app/src/main/java/us/shandian/giga/io/StoredDirectoryHelper.java @@ -10,7 +10,6 @@ import android.os.Build; import android.provider.DocumentsContract; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; import android.support.v4.provider.DocumentFile; import java.io.File; @@ -33,7 +32,6 @@ public class StoredDirectoryHelper { private String tag; - @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public StoredDirectoryHelper(@NonNull Context context, @NonNull Uri path, String tag) throws IOException { this.tag = tag; @@ -50,6 +48,9 @@ public class StoredDirectoryHelper { throw new IOException(e); } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + throw new IOException("Storage Access Framework with Directory API is not available"); + this.docTree = DocumentFile.fromTreeUri(context, path); if (this.docTree == null) diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 8d838ccc2..f25147507 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -1,12 +1,12 @@ package us.shandian.giga.service; import android.Manifest; +import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -20,7 +20,6 @@ import android.net.NetworkRequest; import android.net.Uri; import android.os.Binder; import android.os.Build; -import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -28,6 +27,7 @@ import android.os.Message; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.StringRes; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.content.PermissionChecker; @@ -41,7 +41,6 @@ import org.schabi.newpipe.player.helper.LockManager; import java.io.File; import java.io.IOException; -import java.net.URI; import java.util.ArrayList; import us.shandian.giga.get.DownloadMission; @@ -141,7 +140,7 @@ public class DownloadManagerService extends Service { mPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mManager = new DownloadManager(this, mHandler, getVideoStorage(), getAudioStorage()); + mManager = new DownloadManager(this, mHandler, loadMainVideoStorage(), loadMainAudioStorage()); Intent openDownloadListIntent = new Intent(this, DownloadActivity.class) .setAction(Intent.ACTION_MAIN); @@ -271,6 +270,33 @@ public class DownloadManagerService extends Service { Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show(); } + // Check download save paths + + String msg = ""; + if (mManager.mMainStorageVideo == null) + msg += getString(R.string.download_path_title); + else if (mManager.mMainStorageAudio == null) + msg += getString(R.string.download_path_audio_title); + + if (!msg.isEmpty()) { + String title; + if (mManager.mMainStorageVideo == null && mManager.mMainStorageAudio == null) { + title = getString(R.string.general_error); + msg = getString(R.string.no_available_dir) + ":\n" + msg; + } else { + title = msg; + msg = getString(R.string.no_available_dir); + } + + new AlertDialog.Builder(this) + .setPositiveButton(android.R.string.ok, null) + .setTitle(title) + .setMessage(msg) + .create() + .show(); + } + + return mBinder; } @@ -348,13 +374,10 @@ public class DownloadManagerService extends Service { mManager.mPrefMeteredDownloads = prefs.getBoolean(key, false); } else if (key.equals(getString(R.string.downloads_queue_limit))) { mManager.mPrefQueueLimit = prefs.getBoolean(key, true); - } else if (key.equals(getString(R.string.downloads_storage_api))) { - mManager.mMainStorageVideo = loadMainStorage(getString(R.string.download_path_video_key), DownloadManager.TAG_VIDEO); - mManager.mMainStorageAudio = loadMainStorage(getString(R.string.download_path_audio_key), DownloadManager.TAG_AUDIO); } else if (key.equals(getString(R.string.download_path_video_key))) { - mManager.mMainStorageVideo = loadMainStorage(key, DownloadManager.TAG_VIDEO); + mManager.mMainStorageVideo = loadMainVideoStorage(); } else if (key.equals(getString(R.string.download_path_audio_key))) { - mManager.mMainStorageAudio = loadMainStorage(key, DownloadManager.TAG_AUDIO); + mManager.mMainStorageAudio = loadMainAudioStorage(); } } @@ -385,7 +408,7 @@ public class DownloadManagerService extends Service { * @param psArgs the arguments for the post-processing algorithm. * @param nearLength the approximated final length of the file */ - public static void startMission(Context context, String urls[], StoredFileHelper storage, char kind, + public static void startMission(Context context, String[] urls, StoredFileHelper storage, char kind, int threads, String source, String psName, String[] psArgs, long nearLength) { Intent intent = new Intent(context, DownloadManagerService.class); intent.setAction(Intent.ACTION_RUN); @@ -538,56 +561,28 @@ public class DownloadManagerService extends Service { mLockAcquired = acquire; } - private StoredDirectoryHelper getVideoStorage() { - return loadMainStorage(getString(R.string.download_path_video_key), DownloadManager.TAG_VIDEO); + private StoredDirectoryHelper loadMainVideoStorage() { + return loadMainStorage(R.string.download_path_video_key, DownloadManager.TAG_VIDEO); } - private StoredDirectoryHelper getAudioStorage() { - return loadMainStorage(getString(R.string.download_path_audio_key), DownloadManager.TAG_AUDIO); + private StoredDirectoryHelper loadMainAudioStorage() { + return loadMainStorage(R.string.download_path_audio_key, DownloadManager.TAG_AUDIO); } + private StoredDirectoryHelper loadMainStorage(@StringRes int prefKey, String tag) { + String path = mPrefs.getString(getString(prefKey), null); - private StoredDirectoryHelper loadMainStorage(String prefKey, String tag) { - String path = mPrefs.getString(prefKey, null); - - final String JAVA_IO = getString(R.string.downloads_storage_api_default); - boolean useJavaIO = JAVA_IO.equals(mPrefs.getString(getString(R.string.downloads_storage_api), JAVA_IO)); - - final String defaultPath; - switch (tag) { - case DownloadManager.TAG_VIDEO: - defaultPath = Environment.DIRECTORY_MOVIES; - break; - case DownloadManager.TAG_AUDIO: - defaultPath = Environment.DIRECTORY_MUSIC; - break; - default: - return null; - } - - if (path == null || path.isEmpty()) { - if (useJavaIO) - return new StoredDirectoryHelper(new File(defaultPath).toURI(), tag); - else - return null; - } + if (path == null || path.isEmpty()) return null; if (path.charAt(0) == File.separatorChar) { - Log.i(TAG, "Migrating old save path: " + path); + Log.i(TAG, "Old save path style present: " + path); - useJavaIO = true; - path = Uri.fromFile(new File(path)).toString(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + path = Uri.fromFile(new File(path)).toString(); + else + path = ""; - mPrefs.edit().putString(prefKey, path).apply(); - } - - boolean override = path.startsWith(ContentResolver.SCHEME_FILE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - if (useJavaIO || override) { - return new StoredDirectoryHelper(URI.create(path), tag); - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return null;// SAF Directory API is not available in older versions + mPrefs.edit().putString(getString(prefKey), "").apply(); } try { @@ -619,6 +614,13 @@ public class DownloadManagerService extends Service { return mManager.mMainStorageAudio; } + public boolean askForSavePath() { + return DownloadManagerService.this.mPrefs.getBoolean( + DownloadManagerService.this.getString(R.string.downloads_storage_ask), + false + ); + } + public void addMissionEventListener(Handler handler) { manageObservers(handler, true); } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2e8c62bce..890755845 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -459,19 +459,15 @@ abrir en modo popup Se perdió el progreso porque el archivo fue eliminado Tiempo de espera excedido - API de almacenamiento - Seleccione que API utilizar para almacenar las descargas - - Framework de acceso a almacenamiento - Java I/O - - Guardar como… - No es posible descargar a una tarjeta SD externa. \¿Restablecer la ubicación de la carpeta de descarga\? Seleccione los directorios de descarga Pendiente + Preguntar dónde descargar + Se preguntará dónde guardar cada descarga + Se preguntará dónde guardar cada descarga.\nHabilita esta opción si quieres descargar en la tarjeta SD externa + Desuscribirse Nueva pestaña Elige la pestaña diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 42df857c1..ec288bb18 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -160,20 +160,7 @@ clear_play_history clear_search_history - downloads_storage_api - - - javaIO - - - SAF - javaIO - - - - @string/storage_access_framework_description - @string/java_io_description - + downloads_storage_ask file_rename_charset diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f10a9f589..fb154313e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -550,14 +550,10 @@ Start downloads Pause downloads - Storage API - Select which API use to store the downloads - - Storage Access Framework - Java I/O - - Save as… - Select the downloads save path + Ask where to download + You will be asked where to save each download + You will be asked where to save each download.\nEnable this option if you want download to the external SD Card + \ No newline at end of file diff --git a/app/src/main/res/xml/download_settings.xml b/app/src/main/res/xml/download_settings.xml index 2f62aa89e..7a6fab841 100644 --- a/app/src/main/res/xml/download_settings.xml +++ b/app/src/main/res/xml/download_settings.xml @@ -5,14 +5,12 @@ android:title="@string/settings_category_downloads_title"> - + android:defaultValue="false" + android:key="@string/downloads_storage_ask" + android:summary="@string/downloads_storage_ask_summary_kitkat" + android:title="@string/downloads_storage_ask_title" />