Add support for downloading to internal storage and fix project build.

- Update outdated library com.github.QuadFlask:colorpicker in build.gradle
- Update SettingsActivity with option to select download directory.
- Add migration in PreferenceUtil to support download location storage.
- Update MusicUtil to look for user-provided location when building locations.
- Update DownloadService to download to specified location.
This commit is contained in:
天クマ 2026-01-21 16:50:32 -03:00
commit 5ca55a54d2
8 changed files with 100 additions and 36 deletions

View file

@ -1,12 +1,19 @@
package com.dkanada.gramophone.activities;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
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.util.PreferenceUtil;
import java.io.File;
public class SettingsActivity extends AbsBaseActivity {
private ActivitySettingsBinding binding;
@ -43,6 +52,30 @@ public class SettingsActivity extends AbsBaseActivity {
}
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
public void onCreatePreferences(Bundle bundle, String s) {
addPreferencesFromResource(R.xml.pref_library);
@ -93,14 +126,6 @@ public class SettingsActivity extends AbsBaseActivity {
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) {
// custom notification layouts were removed entirely in Android 12
classicNotification.setEnabled(false);
@ -117,13 +142,32 @@ public class SettingsActivity extends AbsBaseActivity {
return false;
});
// use this to set default state for playback screen and notification style
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext());
downloadLocationPreference.setOnPreferenceClickListener(preference -> {
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.CLASSIC_NOTIFICATION);
}
private void openDirectoryPicker() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
dirPickerLauncher.launch(intent);
}
@Override
@SuppressWarnings("ConstantConditions")
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

View file

@ -2,7 +2,9 @@ package com.dkanada.gramophone.service;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import androidx.documentfile.provider.DocumentFile;
import com.dkanada.gramophone.App;
import com.dkanada.gramophone.BuildConfig;
@ -13,8 +15,6 @@ import com.dkanada.gramophone.util.MusicUtil;
import com.dkanada.gramophone.util.PreferenceUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
@ -76,17 +76,28 @@ public class DownloadService extends Service {
URL url = new URL(MusicUtil.getDownloadUri(song));
URLConnection connection = url.openConnection();
String cache = PreferenceUtil.getInstance(App.getInstance()).getLocationCache();
File download = new File(cache, "download/" + song.id);
File audio = new File(MusicUtil.getFileUri(song));
String location = PreferenceUtil.getInstance(App.getInstance()).getLocationDownload();
DocumentFile root;
if (location.equals(getApplicationContext().getCacheDir().toString())) {
root = DocumentFile.fromFile(new File(location));
} else {
root = DocumentFile.fromTreeUri(this, Uri.parse(location));
}
download.getParentFile().mkdirs();
download.createNewFile();
audio.getParentFile().mkdirs();
audio.createNewFile();
DocumentFile artist = root.findFile(MusicUtil.ascii(song.artistName));
if (artist == null) {
artist = root.createDirectory(MusicUtil.ascii(song.artistName));
}
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();
OutputStream output = new FileOutputStream(download);
OutputStream output = getContentResolver().openOutputStream(audio.getUri());
connection.connect();
@ -102,17 +113,6 @@ public class DownloadService extends Service {
input.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));
notification.stop(song);
} catch (Exception e) {

View file

@ -2,6 +2,7 @@ package com.dkanada.gramophone.util;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@ -78,7 +79,12 @@ public class MusicUtil {
}
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 name = "/" + song.discNumber + "." + song.trackNumber + " - " + ascii(song.title) + "." + song.container;

View file

@ -84,7 +84,6 @@ public final class PreferenceUtil {
public static final String GAIN_OFFSET = "gain_offset";
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 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(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() {
return mPreferences.getString(LOCATION_CACHE, mContext.getCacheDir().toString());
return mContext.getCacheDir().toString();
}
public final long getImageCacheSize() {

View file

@ -107,6 +107,7 @@
<string name="pref_header_library">Library</string>
<string name="pref_header_lock_screen">Lock Screen</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_categories">Categories</string>
@ -143,6 +144,9 @@
<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_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="remove_action">Remove</string>

View file

@ -4,7 +4,7 @@
<com.dkanada.gramophone.views.settings.JellyPreferenceCategory android:title="@string/pref_header_cache">
<com.dkanada.gramophone.views.settings.FilePreference
<Preference
app:iconSpaceReserved="false"
android:key="location_download"
android:title="@string/pref_title_download_location" />

View 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>