Merge pull request #1 from adrianvic/feature-offline-cache
Add support for downloading to internal storage and fix project build.
This commit is contained in:
commit
9eba857973
8 changed files with 100 additions and 36 deletions
|
|
@ -89,7 +89,7 @@ dependencies {
|
||||||
implementation 'com.afollestad:material-cab:2.0.1'
|
implementation 'com.afollestad:material-cab:2.0.1'
|
||||||
|
|
||||||
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
|
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
|
||||||
implementation 'com.github.QuadFlask:colorpicker:ef73ced217'
|
implementation 'com.github.QuadFlask:colorpicker:0.0.15'
|
||||||
implementation 'com.github.codekidX:storage-chooser:2.0.4.4'
|
implementation 'com.github.codekidX:storage-chooser:2.0.4.4'
|
||||||
implementation 'me.relex:circleindicator:2.1.6'
|
implementation 'me.relex:circleindicator:2.1.6'
|
||||||
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
package com.dkanada.gramophone.activities;
|
package com.dkanada.gramophone.activities;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
@ -20,6 +27,8 @@ import com.dkanada.gramophone.dialogs.preferences.NowPlayingPreferenceDialog;
|
||||||
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
|
import com.dkanada.gramophone.activities.base.AbsBaseActivity;
|
||||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class SettingsActivity extends AbsBaseActivity {
|
public class SettingsActivity extends AbsBaseActivity {
|
||||||
private ActivitySettingsBinding binding;
|
private ActivitySettingsBinding binding;
|
||||||
|
|
||||||
|
|
@ -43,6 +52,30 @@ public class SettingsActivity extends AbsBaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public static class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private ActivityResultLauncher<Intent> dirPickerLauncher;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
dirPickerLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
result -> {
|
||||||
|
if (result.getResultCode() == RESULT_OK) {
|
||||||
|
Intent data = result.getData();
|
||||||
|
if (data != null) {
|
||||||
|
Uri uri = data.getData();
|
||||||
|
requireContext().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(requireContext()).edit();
|
||||||
|
editor.putString(PreferenceUtil.LOCATION_DOWNLOAD, uri.toString());
|
||||||
|
editor.apply();
|
||||||
|
invalidateSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle bundle, String s) {
|
public void onCreatePreferences(Bundle bundle, String s) {
|
||||||
addPreferencesFromResource(R.xml.pref_library);
|
addPreferencesFromResource(R.xml.pref_library);
|
||||||
|
|
@ -93,14 +126,6 @@ public class SettingsActivity extends AbsBaseActivity {
|
||||||
blurAlbumCoverPreference.setEnabled(false);
|
blurAlbumCoverPreference.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
|
|
||||||
downloadLocationPreference.setEnabled(false);
|
|
||||||
|
|
||||||
// stock Android 11 removed the album cover on lock screens
|
|
||||||
// supported on LineageOS so we might want to add a check at some point
|
|
||||||
showAlbumCoverPreference.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||||
// custom notification layouts were removed entirely in Android 12
|
// custom notification layouts were removed entirely in Android 12
|
||||||
classicNotification.setEnabled(false);
|
classicNotification.setEnabled(false);
|
||||||
|
|
@ -117,13 +142,32 @@ public class SettingsActivity extends AbsBaseActivity {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// use this to set default state for playback screen and notification style
|
downloadLocationPreference.setOnPreferenceClickListener(preference -> {
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
openDirectoryPicker();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext());
|
||||||
|
String downloadLocation = preferences.getString(PreferenceUtil.LOCATION_DOWNLOAD, null);
|
||||||
|
if (downloadLocation != null) {
|
||||||
|
Uri uri = Uri.parse(downloadLocation);
|
||||||
|
File file = new File(uri.getPath());
|
||||||
|
downloadLocationPreference.setSummary(file.getPath());
|
||||||
|
} else {
|
||||||
|
downloadLocationPreference.setSummary(R.string.pref_title_download_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// use this to set default state for playback screen and notification style
|
||||||
onSharedPreferenceChanged(preferences, PreferenceUtil.NOW_PLAYING_SCREEN);
|
onSharedPreferenceChanged(preferences, PreferenceUtil.NOW_PLAYING_SCREEN);
|
||||||
onSharedPreferenceChanged(preferences, PreferenceUtil.CLASSIC_NOTIFICATION);
|
onSharedPreferenceChanged(preferences, PreferenceUtil.CLASSIC_NOTIFICATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openDirectoryPicker() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
dirPickerLauncher.launch(intent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package com.dkanada.gramophone.service;
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
||||||
import com.dkanada.gramophone.App;
|
import com.dkanada.gramophone.App;
|
||||||
import com.dkanada.gramophone.BuildConfig;
|
import com.dkanada.gramophone.BuildConfig;
|
||||||
|
|
@ -13,8 +15,6 @@ import com.dkanada.gramophone.util.MusicUtil;
|
||||||
import com.dkanada.gramophone.util.PreferenceUtil;
|
import com.dkanada.gramophone.util.PreferenceUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
@ -76,17 +76,28 @@ public class DownloadService extends Service {
|
||||||
URL url = new URL(MusicUtil.getDownloadUri(song));
|
URL url = new URL(MusicUtil.getDownloadUri(song));
|
||||||
URLConnection connection = url.openConnection();
|
URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
String cache = PreferenceUtil.getInstance(App.getInstance()).getLocationCache();
|
String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload();
|
||||||
File download = new File(cache, "download/" + song.id);
|
DocumentFile root;
|
||||||
File audio = new File(MusicUtil.getFileUri(song));
|
if (location.equals(getApplicationContext().getCacheDir().toString())) {
|
||||||
|
root = DocumentFile.fromFile(new File(location));
|
||||||
|
} else {
|
||||||
|
root = DocumentFile.fromTreeUri(this, Uri.parse(location));
|
||||||
|
}
|
||||||
|
|
||||||
download.getParentFile().mkdirs();
|
DocumentFile artist = root.findFile(MusicUtil.ascii(song.artistName));
|
||||||
download.createNewFile();
|
if (artist == null) {
|
||||||
audio.getParentFile().mkdirs();
|
artist = root.createDirectory(MusicUtil.ascii(song.artistName));
|
||||||
audio.createNewFile();
|
}
|
||||||
|
DocumentFile album = artist.findFile(MusicUtil.ascii(song.albumName));
|
||||||
|
if (album == null) {
|
||||||
|
album = artist.createDirectory(MusicUtil.ascii(song.albumName));
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = song.discNumber + "." + song.trackNumber + " - " + MusicUtil.ascii(song.title) + "." + song.container;
|
||||||
|
DocumentFile audio = album.createFile("audio/" + song.container, fileName);
|
||||||
|
|
||||||
InputStream input = connection.getInputStream();
|
InputStream input = connection.getInputStream();
|
||||||
OutputStream output = new FileOutputStream(download);
|
OutputStream output = getContentResolver().openOutputStream(audio.getUri());
|
||||||
|
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
|
||||||
|
|
@ -102,17 +113,6 @@ public class DownloadService extends Service {
|
||||||
input.close();
|
input.close();
|
||||||
output.close();
|
output.close();
|
||||||
|
|
||||||
input = new FileInputStream(download);
|
|
||||||
output = new FileOutputStream(audio);
|
|
||||||
|
|
||||||
while ((count = input.read(data)) != -1) {
|
|
||||||
output.write(data, 0, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.close();
|
|
||||||
output.close();
|
|
||||||
|
|
||||||
download.delete();
|
|
||||||
App.getDatabase().cacheDao().insertCache(new Cache(song));
|
App.getDatabase().cacheDao().insertCache(new Cache(song));
|
||||||
notification.stop(song);
|
notification.stop(song);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.dkanada.gramophone.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
@ -78,7 +79,12 @@ public class MusicUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getFileUri(Song song) {
|
public static String getFileUri(Song song) {
|
||||||
File root = new File(PreferenceUtil.getInstance(App.getInstance()).getLocationDownload(), "music");
|
String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload();
|
||||||
|
File root = new File(location, "music");
|
||||||
|
if (!location.equals(App.getInstance().getCacheDir().toString())) {
|
||||||
|
Uri uri = Uri.parse(location);
|
||||||
|
return new File(uri.getPath()).getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
String path = "/" + ascii(song.artistName) + "/" + ascii(song.albumName);
|
String path = "/" + ascii(song.artistName) + "/" + ascii(song.albumName);
|
||||||
String name = "/" + song.discNumber + "." + song.trackNumber + " - " + ascii(song.title) + "." + song.container;
|
String name = "/" + song.discNumber + "." + song.trackNumber + " - " + ascii(song.title) + "." + song.container;
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@ public final class PreferenceUtil {
|
||||||
public static final String GAIN_OFFSET = "gain_offset";
|
public static final String GAIN_OFFSET = "gain_offset";
|
||||||
|
|
||||||
public static final String LOCATION_DOWNLOAD = "location_download";
|
public static final String LOCATION_DOWNLOAD = "location_download";
|
||||||
public static final String LOCATION_CACHE = "location_cache";
|
|
||||||
public static final String IMAGE_CACHE_SIZE = "image_cache_size";
|
public static final String IMAGE_CACHE_SIZE = "image_cache_size";
|
||||||
public static final String MEDIA_CACHE_SIZE = "media_cache_size";
|
public static final String MEDIA_CACHE_SIZE = "media_cache_size";
|
||||||
|
|
||||||
|
|
@ -97,7 +96,7 @@ public final class PreferenceUtil {
|
||||||
|
|
||||||
preferences.edit().putString(GENERAL_THEME, Theme.valueOf(theme.toUpperCase()).toString()).commit();
|
preferences.edit().putString(GENERAL_THEME, Theme.valueOf(theme.toUpperCase()).toString()).commit();
|
||||||
preferences.edit().putString(IMAGE_CACHE_SIZE, imageSize.substring(0, imageSize.length() - 6)).commit();
|
preferences.edit().putString(IMAGE_CACHE_SIZE, imageSize.substring(0, imageSize.length() - 6)).commit();
|
||||||
preferences.edit().putString(MEDIA_CACHE_SIZE, mediaSize.substring(0, imageSize.length() - 6)).commit();
|
preferences.edit().putString(MEDIA_CACHE_SIZE, mediaSize.substring(0, mediaSize.length() - 6)).commit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -424,7 +423,7 @@ public final class PreferenceUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final String getLocationCache() {
|
public final String getLocationCache() {
|
||||||
return mPreferences.getString(LOCATION_CACHE, mContext.getCacheDir().toString());
|
return mContext.getCacheDir().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getImageCacheSize() {
|
public final long getImageCacheSize() {
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@
|
||||||
<string name="pref_header_library">Library</string>
|
<string name="pref_header_library">Library</string>
|
||||||
<string name="pref_header_lock_screen">Lock Screen</string>
|
<string name="pref_header_lock_screen">Lock Screen</string>
|
||||||
<string name="pref_header_notification">Notification</string>
|
<string name="pref_header_notification">Notification</string>
|
||||||
|
<string name="pref_header_storage">Storage</string>
|
||||||
|
|
||||||
<string name="pref_title_direct_play_codecs">Direct Play Codecs</string>
|
<string name="pref_title_direct_play_codecs">Direct Play Codecs</string>
|
||||||
<string name="pref_title_categories">Categories</string>
|
<string name="pref_title_categories">Categories</string>
|
||||||
|
|
@ -143,6 +144,9 @@
|
||||||
<string name="pref_summary_categories">Configure visibility and order of display categories.</string>
|
<string name="pref_summary_categories">Configure visibility and order of display categories.</string>
|
||||||
<string name="pref_summary_direct_play_codecs">Disable direct play codecs to force transcoding.</string>
|
<string name="pref_summary_direct_play_codecs">Disable direct play codecs to force transcoding.</string>
|
||||||
<string name="pref_summary_gain_adjustment">Adjust the gain of the music player\'s output</string>
|
<string name="pref_summary_gain_adjustment">Adjust the gain of the music player\'s output</string>
|
||||||
|
<string name="save_lyrics">Save Lyrics</string>
|
||||||
|
<string name="cached_songs_location">Cached Songs Location</string>
|
||||||
|
<string name="cached_songs_location_summary">Select where to store cached songs</string>
|
||||||
|
|
||||||
<string name="delete_action">Delete</string>
|
<string name="delete_action">Delete</string>
|
||||||
<string name="remove_action">Remove</string>
|
<string name="remove_action">Remove</string>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<com.dkanada.gramophone.views.settings.JellyPreferenceCategory android:title="@string/pref_header_cache">
|
<com.dkanada.gramophone.views.settings.JellyPreferenceCategory android:title="@string/pref_header_cache">
|
||||||
|
|
||||||
<com.dkanada.gramophone.views.settings.FilePreference
|
<Preference
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
android:key="location_download"
|
android:key="location_download"
|
||||||
android:title="@string/pref_title_download_location" />
|
android:title="@string/pref_title_download_location" />
|
||||||
|
|
|
||||||
11
app/src/main/res/xml/pref_storage.xml
Normal file
11
app/src/main/res/xml/pref_storage.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="save_lyrics"
|
||||||
|
app:title="@string/save_lyrics"
|
||||||
|
app:defaultValue="false" />
|
||||||
|
<Preference
|
||||||
|
app:key="cached_songs_location"
|
||||||
|
app:title="@string/cached_songs_location"
|
||||||
|
app:summary="@string/cached_songs_location_summary" />
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue