diff --git a/app/build.gradle b/app/build.gradle index 76738816..1f7169d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,14 +9,14 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 31 renderscriptTargetApi 29//must match target sdk and build tools vectorDrawables.useSupportLibrary = true applicationId "code.name.monkey.retromusic" - versionCode 10519 - versionName '5.0.0' + "_" + getDate() + versionCode 10537 + versionName '5.2.1 ' + "_" + getDate() buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"") } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 26baf707..bf69e2ee 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,6 +35,7 @@ tools:targetApi="m"> @@ -144,6 +145,7 @@ - + - + @@ -191,7 +197,7 @@ @@ -204,7 +210,7 @@ @@ -216,7 +222,7 @@ @@ -228,7 +234,7 @@ @@ -240,7 +246,7 @@ @@ -274,17 +280,6 @@ android:name="com.android.vending.splits.required" android:value="true" /> - - - - - : AbsBaseActivity() { private var savedArtworkInfo: ArtworkInfo? = null private var _binding: VB? = null protected val binding: VB get() = _binding!! + private var cacheFiles = listOf() abstract val bindingInflater: (LayoutInflater) -> VB + private lateinit var launcher: ActivityResultLauncher + protected abstract fun loadImageFromFile(selectedFile: Uri?) protected val show: AlertDialog @@ -195,7 +206,6 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { super.onCreate(savedInstanceState) _binding = bindingInflater.invoke(layoutInflater) setContentView(binding.root) - setStatusbarColorAuto() setTaskDescriptionColorAuto() saveFab = findViewById(R.id.saveTags) @@ -207,6 +217,11 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { finish() } setUpViews() + launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { + if (it.resultCode == Activity.RESULT_OK) { + writeToFiles(getSongUris(), cacheFiles) + } + } } private fun setUpViews() { @@ -265,6 +280,8 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { protected abstract fun getSongPaths(): List + protected abstract fun getSongUris(): List + protected fun searchWebFor(vararg keys: String) { val stringBuilder = StringBuilder() for (key in keys) { @@ -336,23 +353,53 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { hideFab() println(fieldKeyValueMap) - WriteTagsAsyncTask(this).execute( - LoadingInfo( - songPaths, - fieldKeyValueMap, - artworkInfo - ) - ) + GlobalScope.launch { + if (VersionUtils.hasR()) { + cacheFiles = TagWriter.writeTagsToFilesR( + this@AbsTagEditorActivity, AudioTagInfo( + songPaths, + fieldKeyValueMap, + artworkInfo + ) + ) + val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris()) + + launcher.launch(IntentSenderRequest.Builder(pendingIntent).build()) + } else { + TagWriter.writeTagsToFiles( + this@AbsTagEditorActivity, AudioTagInfo( + songPaths, + fieldKeyValueMap, + artworkInfo + ) + ) + } + } } private fun writeTags(paths: List?) { - WriteTagsAsyncTask(this).execute( - LoadingInfo( - paths, - savedTags, - savedArtworkInfo - ) - ) + GlobalScope.launch { + if (VersionUtils.hasR()) { + cacheFiles = TagWriter.writeTagsToFilesR( + this@AbsTagEditorActivity, AudioTagInfo( + paths, + savedTags, + savedArtworkInfo + ) + ) + val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris()) + + launcher.launch(IntentSenderRequest.Builder(pendingIntent).build()) + } else { + TagWriter.writeTagsToFiles( + this@AbsTagEditorActivity, AudioTagInfo( + paths, + savedTags, + savedArtworkInfo + ) + ) + } + } } @@ -391,9 +438,30 @@ abstract class AbsTagEditorActivity : AbsBaseActivity() { } } + private fun writeToFiles(songUris: List, cacheFiles: List) { + if (cacheFiles.size == songUris.size) { + for (i in cacheFiles.indices) { + contentResolver.openOutputStream(songUris[i])?.use { output -> + cacheFiles[i].inputStream().use { input -> + input.copyTo(output) + } + } + } + } + lifecycleScope.launch { + TagWriter.scan(this@AbsTagEditorActivity, getSongPaths()) + } + } + + override fun onDestroy() { + super.onDestroy() + // Delete Cache Files + cacheFiles.forEach { file -> + file.delete() + } + } companion object { - const val EXTRA_ID = "extra_id" const val EXTRA_PALETTE = "extra_palette" private val TAG = AbsTagEditorActivity::class.java.simpleName diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt index 6bbae4d9..53acf609 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/AlbumTagEditorActivity.kt @@ -38,6 +38,7 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.ImageUtil +import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.getColor import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -193,6 +194,11 @@ class AlbumTagEditorActivity : AbsTagEditorActivity = repository.albumById(id).songs.map { + MusicUtil.getSongFileUri(it.id) + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt index 34e3d3f8..8472f766 100755 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/SongTagEditorActivity.kt @@ -27,6 +27,7 @@ import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.extensions.setTint import code.name.monkey.retromusic.repository.SongRepository +import code.name.monkey.retromusic.util.MusicUtil import org.jaudiotagger.tag.FieldKey import org.koin.android.ext.android.inject import java.util.* @@ -111,6 +112,8 @@ class SongTagEditorActivity : AbsTagEditorActivity override fun getSongPaths(): List = listOf(songRepository.song(id).data) + override fun getSongUris(): List = listOf(MusicUtil.getSongFileUri(id)) + override fun loadImageFromFile(selectedFile: Uri?) { } diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt new file mode 100644 index 00000000..39f24b44 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/TagWriter.kt @@ -0,0 +1,203 @@ +package code.name.monkey.retromusic.activities.tageditor + +import android.app.Activity +import android.content.Context +import android.graphics.Bitmap +import android.media.MediaScannerConnection +import android.os.Build +import android.util.Log +import android.widget.Toast +import androidx.annotation.RequiresApi +import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener +import code.name.monkey.retromusic.model.AudioTagInfo +import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile +import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt +import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jaudiotagger.audio.AudioFileIO +import org.jaudiotagger.audio.exceptions.CannotReadException +import org.jaudiotagger.audio.exceptions.CannotWriteException +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException +import org.jaudiotagger.tag.TagException +import org.jaudiotagger.tag.images.Artwork +import org.jaudiotagger.tag.images.ArtworkFactory +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +class TagWriter { + + companion object { + + suspend fun scan(context: Context, toBeScanned: List?) { + if (toBeScanned == null || toBeScanned.isEmpty()) { + Log.i("scan", "scan: Empty") + Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show() + return + } + MediaScannerConnection.scanFile( + context, + toBeScanned.toTypedArray(), + null, + withContext(Dispatchers.Main) { + if (context is Activity) UpdateToastMediaScannerCompletionListener( + context, toBeScanned + ) else null + } + ) + } + + suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) = + withContext(Dispatchers.IO) { + try { + var artwork: Artwork? = null + var albumArtFile: File? = null + if (info.artworkInfo?.artwork != null) { + try { + albumArtFile = createAlbumArtFile(context).canonicalFile + info.artworkInfo.artwork.compress( + Bitmap.CompressFormat.PNG, + 0, + FileOutputStream(albumArtFile) + ) + artwork = ArtworkFactory.createArtworkFromFile(albumArtFile) + } catch (e: IOException) { + e.printStackTrace() + } + } + var wroteArtwork = false + var deletedArtwork = false + for (filePath in info.filePaths!!) { + try { + val audioFile = AudioFileIO.read(File(filePath)) + val tag = audioFile.tagOrCreateAndSetDefault + if (info.fieldKeyValueMap != null) { + for ((key, value) in info.fieldKeyValueMap) { + try { + tag.setField(key, value) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + if (info.artworkInfo != null) { + if (info.artworkInfo.artwork == null) { + tag.deleteArtworkField() + deletedArtwork = true + } else if (artwork != null) { + tag.deleteArtworkField() + tag.setField(artwork) + wroteArtwork = true + } + } + audioFile.commit() + } catch (e: CannotReadException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } catch (e: CannotWriteException) { + e.printStackTrace() + } catch (e: TagException) { + e.printStackTrace() + } catch (e: ReadOnlyFileException) { + e.printStackTrace() + } catch (e: InvalidAudioFrameException) { + e.printStackTrace() + } + } + if (wroteArtwork) { + insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path) + } else if (deletedArtwork) { + deleteAlbumArt(context, info.artworkInfo!!.albumId) + } + scan(context, info.filePaths) + } catch (e: Exception) { + e.printStackTrace() + scan(context, null) + } + } + + + @RequiresApi(Build.VERSION_CODES.R) + suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo) = + withContext(Dispatchers.IO) { + val cacheFiles = mutableListOf() + try { + var artwork: Artwork? = null + var albumArtFile: File? = null + if (info.artworkInfo?.artwork != null) { + try { + albumArtFile = createAlbumArtFile(context).canonicalFile + info.artworkInfo.artwork.compress( + Bitmap.CompressFormat.PNG, + 0, + FileOutputStream(albumArtFile) + ) + artwork = ArtworkFactory.createArtworkFromFile(albumArtFile) + } catch (e: IOException) { + e.printStackTrace() + } + } + var wroteArtwork = false + var deletedArtwork = false + for (filePath in info.filePaths!!) { + try { + val originFile = File(filePath) + val cacheFile = File(context.cacheDir, originFile.name) + cacheFiles.add(cacheFile) + originFile.inputStream().use { input -> + cacheFile.outputStream().use { output -> + input.copyTo(output) + } + } + val audioFile = AudioFileIO.read(cacheFile) + val tag = audioFile.tagOrCreateAndSetDefault + if (info.fieldKeyValueMap != null) { + for ((key, value) in info.fieldKeyValueMap) { + try { + tag.setField(key, value) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + if (info.artworkInfo != null) { + if (info.artworkInfo.artwork == null) { + tag.deleteArtworkField() + deletedArtwork = true + } else if (artwork != null) { + tag.deleteArtworkField() + tag.setField(artwork) + wroteArtwork = true + } + } + audioFile.commit() + } catch (e: CannotReadException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } catch (e: CannotWriteException) { + e.printStackTrace() + } catch (e: TagException) { + e.printStackTrace() + } catch (e: ReadOnlyFileException) { + e.printStackTrace() + } catch (e: InvalidAudioFrameException) { + e.printStackTrace() + } + } + if (wroteArtwork) { + insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path) + } else if (deletedArtwork) { + deleteAlbumArt(context, info.artworkInfo!!.albumId) + } + cacheFiles + } catch (e: Exception) { + e.printStackTrace() + listOf() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java b/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java deleted file mode 100644 index 0ae1dc8d..00000000 --- a/app/src/main/java/code/name/monkey/retromusic/activities/tageditor/WriteTagsAsyncTask.java +++ /dev/null @@ -1,152 +0,0 @@ -package code.name.monkey.retromusic.activities.tageditor; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.graphics.Bitmap; -import android.media.MediaScannerConnection; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.NonNull; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.jaudiotagger.audio.AudioFile; -import org.jaudiotagger.audio.AudioFileIO; -import org.jaudiotagger.audio.exceptions.CannotReadException; -import org.jaudiotagger.audio.exceptions.CannotWriteException; -import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; -import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; -import org.jaudiotagger.tag.FieldKey; -import org.jaudiotagger.tag.Tag; -import org.jaudiotagger.tag.TagException; -import org.jaudiotagger.tag.images.Artwork; -import org.jaudiotagger.tag.images.ArtworkFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import code.name.monkey.retromusic.R; -import code.name.monkey.retromusic.misc.DialogAsyncTask; -import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener; -import code.name.monkey.retromusic.model.LoadingInfo; -import code.name.monkey.retromusic.util.MusicUtil; - -public class WriteTagsAsyncTask extends DialogAsyncTask> { - - public WriteTagsAsyncTask(Context context) { - super(context); - } - - @Override - protected List doInBackground(LoadingInfo... params) { - try { - LoadingInfo info = params[0]; - - Artwork artwork = null; - File albumArtFile = null; - if (info.getArtworkInfo() != null && info.getArtworkInfo().getArtwork() != null) { - try { - albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile(); - info.getArtworkInfo().getArtwork().compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile)); - artwork = ArtworkFactory.createArtworkFromFile(albumArtFile); - } catch (IOException e) { - e.printStackTrace(); - } - } - - int counter = 0; - boolean wroteArtwork = false; - boolean deletedArtwork = false; - for (String filePath : info.getFilePaths()) { - publishProgress(++counter, info.getFilePaths().size()); - try { - AudioFile audioFile = AudioFileIO.read(new File(filePath)); - Tag tag = audioFile.getTagOrCreateAndSetDefault(); - - if (info.getFieldKeyValueMap() != null) { - for (Map.Entry entry : info.getFieldKeyValueMap().entrySet()) { - try { - tag.setField(entry.getKey(), entry.getValue()); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - if (info.getArtworkInfo() != null) { - if (info.getArtworkInfo().getArtwork() == null) { - tag.deleteArtworkField(); - deletedArtwork = true; - } else if (artwork != null) { - tag.deleteArtworkField(); - tag.setField(artwork); - wroteArtwork = true; - } - } - - audioFile.commit(); - } catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { - e.printStackTrace(); - } - } - - Context context = getContext(); - if (context != null) { - if (wroteArtwork) { - MusicUtil.INSTANCE. - insertAlbumArt(context, info.getArtworkInfo().getAlbumId(), albumArtFile.getPath()); - } else if (deletedArtwork) { - MusicUtil.INSTANCE.deleteAlbumArt(context, info.getArtworkInfo().getAlbumId()); - } - } - - return info.getFilePaths(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - @Override - protected void onPostExecute(List toBeScanned) { - super.onPostExecute(toBeScanned); - scan(toBeScanned); - } - - @Override - protected void onCancelled(List toBeScanned) { - super.onCancelled(toBeScanned); - scan(toBeScanned); - } - - private void scan(List toBeScanned) { - Context context = getContext(); - if (toBeScanned == null || toBeScanned.isEmpty()) { - Log.i("scan", "scan: Empty"); - Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show(); - return; - } - MediaScannerConnection.scanFile(context, toBeScanned.toArray(new String[0]), null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null); - } - - @Override - protected Dialog createDialog(@NonNull Context context) { - return new MaterialAlertDialogBuilder(context) - .setTitle(R.string.saving_changes) - .setCancelable(false) - .setView(R.layout.loading) - .create(); - } - - @Override - protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) { - super.onProgressUpdate(dialog, values); -// ((MaterialDialog) dialog).setMaxProgress(values[1]); -// ((MaterialDialog) dialog).setProgress(values[0]); - } -} diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt index 461b3e27..68596c69 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetBig.kt @@ -74,7 +74,7 @@ class AppWidgetBig : BaseAppWidget() { ) ) appWidgetView.setImageViewBitmap( - R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap( + R.id.button_toggle_play_pause, BaseAppWidget.createBitmap( RetroUtil.getTintedVectorDrawable( context, R.drawable.ic_play_arrow_white_32dp, @@ -202,13 +202,13 @@ class AppWidgetBig : BaseAppWidget() { MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel ) - var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) // Home action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - pendingIntent = PendingIntent.getActivity(context, 0, action, 0) + var pendingIntent = + PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE) views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent) // Previous track diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt index a0729267..30bcf124 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetCard.kt @@ -141,11 +141,11 @@ class AppWidgetCard : BaseAppWidget() { if (imageSize == 0) { imageSize = - service.resources.getDimensionPixelSize(code.name.monkey.retromusic.R.dimen.app_widget_card_image_size) + service.resources.getDimensionPixelSize(R.dimen.app_widget_card_image_size) } if (cardRadius == 0f) { cardRadius = - service.resources.getDimension(code.name.monkey.retromusic.R.dimen.app_widget_card_radius) + service.resources.getDimension(R.dimen.app_widget_card_radius) } // Load the album cover async and push the update on completion @@ -225,13 +225,13 @@ class AppWidgetCard : BaseAppWidget() { MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel ) - var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) // Home action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - pendingIntent = PendingIntent.getActivity(context, 0, action, 0) + var pendingIntent = + PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE) views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt index 3d00ee71..134938d2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetClassic.kt @@ -216,13 +216,12 @@ class AppWidgetClassic : BaseAppWidget() { MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel ) - var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) // Home action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - pendingIntent = PendingIntent.getActivity(context, 0, action, 0) + var pendingIntent = PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE) views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt index 14795cd3..4528abce 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetSmall.kt @@ -201,13 +201,13 @@ class AppWidgetSmall : BaseAppWidget() { MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel ) - var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) // Home action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - pendingIntent = PendingIntent.getActivity(context, 0, action, 0) + var pendingIntent = + PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE) views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt index 961717d1..b4abebf2 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/AppWidgetText.kt @@ -18,6 +18,7 @@ import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent +import android.provider.MediaStore import android.text.TextUtils import android.view.View import android.widget.RemoteViews @@ -83,13 +84,12 @@ class AppWidgetText : BaseAppWidget() { MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel ) - var pendingIntent: PendingIntent val serviceName = ComponentName(context, MusicService::class.java) // Home action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - pendingIntent = PendingIntent.getActivity(context, 0, action, 0) + var pendingIntent = PendingIntent.getActivity(context, 0, action, PendingIntent.FLAG_IMMUTABLE) views.setOnClickPendingIntent(R.id.image, pendingIntent) views.setOnClickPendingIntent(R.id.media_titles, pendingIntent) diff --git a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt index 5172a76b..03a3187e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt +++ b/app/src/main/java/code/name/monkey/retromusic/appwidgets/base/BaseAppWidget.kt @@ -97,9 +97,9 @@ abstract class BaseAppWidget : AppWidgetProvider() { val intent = Intent(action) intent.component = serviceName return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(context, 0, intent, 0) + PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } else { - PendingIntent.getService(context, 0, intent, 0) + PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } } diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt index 1a528412..dc643849 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/DeleteSongsDialog.kt @@ -21,6 +21,7 @@ import android.os.Bundle import androidx.core.os.bundleOf import androidx.core.text.HtmlCompat import androidx.fragment.app.DialogFragment +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.saf.SAFGuideActivity @@ -89,7 +90,11 @@ class DeleteSongsDialog : DialogFragment() { if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) { MusicPlayerRemote.playNextSong() } - if (!SAFUtil.isSAFRequiredForSongs(songs)) { + if (VersionUtils.hasQ()) { + dismiss() + MusicUtil.deleteTracksQ(requireActivity(), songs) + reloadTabs() + } else if (!SAFUtil.isSAFRequiredForSongs(songs)) { CoroutineScope(Dispatchers.IO).launch { dismiss() MusicUtil.deleteTracks(requireContext(), songs) diff --git a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt index 62770536..ef4dd7cc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt +++ b/app/src/main/java/code/name/monkey/retromusic/dialogs/SavePlaylistDialog.kt @@ -21,13 +21,16 @@ import android.widget.Toast import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.App import code.name.monkey.retromusic.EXTRA_PLAYLIST import code.name.monkey.retromusic.R import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.extensions.colorButtons +import code.name.monkey.retromusic.extensions.createNewFile import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.materialDialog +import code.name.monkey.retromusic.helper.M3UWriter import code.name.monkey.retromusic.util.PlaylistsUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -46,22 +49,59 @@ class SavePlaylistDialog : DialogFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - lifecycleScope.launch(Dispatchers.IO) { - val playlistWithSongs = extraNotNull(EXTRA_PLAYLIST).value - val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs) - MediaScannerConnection.scanFile( - requireActivity(), - arrayOf(file.path), - null - ) { _, _ -> + val playlistWithSongs = extraNotNull(EXTRA_PLAYLIST).value + + if (VersionUtils.hasR()) { + createNewFile( + "audio/mpegurl", + playlistWithSongs.playlistEntity.playlistName + ) { outputStream, data -> + try { + if (outputStream != null) { + lifecycleScope.launch(Dispatchers.IO) { + M3UWriter.writeIO( + outputStream, + playlistWithSongs + ) + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + String.format( + requireContext().getString(R.string.saved_playlist_to), + data?.lastPathSegment + ), + Toast.LENGTH_LONG + ).show() + dismiss() + } + } + } + } catch (e: Exception) { + Toast.makeText( + context, + "Something went wrong : " + e.message, + Toast.LENGTH_SHORT + ) + .show() + } } - withContext(Dispatchers.Main) { - Toast.makeText( - requireContext(), - String.format(App.getContext().getString(R.string.saved_playlist_to), file), - Toast.LENGTH_LONG - ).show() - dismiss() + } else { + lifecycleScope.launch(Dispatchers.IO) { + val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs) + MediaScannerConnection.scanFile( + requireActivity(), + arrayOf(file.path), + null + ) { _, _ -> + } + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + String.format(App.getContext().getString(R.string.saved_playlist_to), file), + Toast.LENGTH_LONG + ).show() + dismiss() + } } } } diff --git a/app/src/main/java/code/name/monkey/retromusic/extensions/IntentExtensions.kt b/app/src/main/java/code/name/monkey/retromusic/extensions/IntentExtensions.kt new file mode 100644 index 00000000..3c1f3988 --- /dev/null +++ b/app/src/main/java/code/name/monkey/retromusic/extensions/IntentExtensions.kt @@ -0,0 +1,35 @@ +package code.name.monkey.retromusic.extensions + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Environment +import android.provider.DocumentsContract +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.documentfile.provider.DocumentFile +import androidx.fragment.app.Fragment +import java.io.File +import java.io.OutputStream + +fun Fragment.createNewFile( + mimeType: String, + fileName: String, + write: (outputStream: OutputStream?, data: Uri?) -> Unit +) { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = mimeType + intent.putExtra(Intent.EXTRA_TITLE, fileName) + val startForResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) + { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + val outputStream: OutputStream? = + context?.contentResolver?.openOutputStream(result.data?.data!!) + write(outputStream, result.data?.data) + } + + } + startForResult.launch(intent) +} \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/other/LyricsFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/other/LyricsFragment.kt index a197ddb6..4b43f4ea 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/other/LyricsFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/other/LyricsFragment.kt @@ -14,19 +14,22 @@ */ package code.name.monkey.retromusic.fragments.other +import android.annotation.SuppressLint import android.os.Bundle import android.text.InputType import android.view.* import androidx.core.view.ViewCompat +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.findNavController import androidx.transition.Fade import androidx.viewpager2.adapter.FragmentStateAdapter import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity -import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask +import code.name.monkey.retromusic.activities.tageditor.TagWriter import code.name.monkey.retromusic.databinding.FragmentLyricsBinding import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding @@ -37,7 +40,7 @@ import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.lyrics.LrcView -import code.name.monkey.retromusic.model.LoadingInfo +import code.name.monkey.retromusic.model.AudioTagInfo import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.RetroUtil @@ -107,6 +110,9 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) { setupViews() setupToolbar() updateTitleSong() + if (VersionUtils.hasR()) { + binding.editButton.isVisible = false + } } private fun setupViews() { @@ -187,6 +193,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) { } + @SuppressLint("CheckResult") private fun editNormalLyrics() { var content = "" val file = File(MusicPlayerRemote.currentSong.data) @@ -205,11 +212,13 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) { ) { _, input -> val fieldKeyValueMap = EnumMap(FieldKey::class.java) fieldKeyValueMap[FieldKey.LYRICS] = input.toString() - WriteTagsAsyncTask(requireActivity()).execute( - LoadingInfo( - listOf(song.data), fieldKeyValueMap, null + GlobalScope.launch { + TagWriter.writeTagsToFiles( + requireContext(), AudioTagInfo( + listOf(song.data), fieldKeyValueMap, null + ) ) - ) + } } positiveButton(res = R.string.save) { (lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics() @@ -219,6 +228,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) { } + @SuppressLint("CheckResult") private fun editSyncedLyrics() { var lrcFile: File? = null if (LyricUtil.isLrcOriginalFileExist(song.data)) { diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt index 293d3d38..c834b9a5 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/M3UWriter.kt @@ -18,10 +18,7 @@ import code.name.monkey.retromusic.db.PlaylistWithSongs import code.name.monkey.retromusic.db.toSongs import code.name.monkey.retromusic.model.Playlist import code.name.monkey.retromusic.model.Song -import java.io.BufferedWriter -import java.io.File -import java.io.FileWriter -import java.io.IOException +import java.io.* object M3UWriter : M3UConstants { @JvmStatic @@ -69,4 +66,23 @@ object M3UWriter : M3UConstants { } return file } + + fun writeIO(outputStream: OutputStream, playlistWithSongs: PlaylistWithSongs) { + val songs: List = playlistWithSongs.songs.sortedBy { + it.songPrimaryKey + }.toSongs() + if (songs.isNotEmpty()) { + val bufferedWriter = outputStream.bufferedWriter() + bufferedWriter.write(M3UConstants.HEADER) + songs.forEach { + bufferedWriter.newLine() + bufferedWriter.write(M3UConstants.ENTRY + it.duration + M3UConstants.DURATION_SEPARATOR + it.artistName + " - " + it.title) + bufferedWriter.newLine() + bufferedWriter.write(it.data) + } + bufferedWriter.close() + } + outputStream.flush() + outputStream.close() + } } diff --git a/app/src/main/java/code/name/monkey/retromusic/model/LoadingInfo.kt b/app/src/main/java/code/name/monkey/retromusic/model/AudioTagInfo.kt similarity index 90% rename from app/src/main/java/code/name/monkey/retromusic/model/LoadingInfo.kt rename to app/src/main/java/code/name/monkey/retromusic/model/AudioTagInfo.kt index bfb9751f..3ac895df 100644 --- a/app/src/main/java/code/name/monkey/retromusic/model/LoadingInfo.kt +++ b/app/src/main/java/code/name/monkey/retromusic/model/AudioTagInfo.kt @@ -2,7 +2,7 @@ package code.name.monkey.retromusic.model import org.jaudiotagger.tag.FieldKey -class LoadingInfo( +class AudioTagInfo( val filePaths: List?, val fieldKeyValueMap: Map?, val artworkInfo: ArtworkInfo? diff --git a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java index 95799879..949d1130 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java +++ b/app/src/main/java/code/name/monkey/retromusic/service/MusicService.java @@ -189,13 +189,13 @@ public class MusicService extends MediaBrowserServiceCompat private final AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance(); - private final AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); + private final AppWidgetCard appWidgetCard = AppWidgetCard.Companion.getInstance(); - private final AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); + private final AppWidgetClassic appWidgetClassic = AppWidgetClassic.Companion.getInstance(); - private final AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); + private final AppWidgetSmall appWidgetSmall = AppWidgetSmall.Companion.getInstance(); - private final AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); + private final AppWidgetText appWidgetText = AppWidgetText.Companion.getInstance(); private final BroadcastReceiver widgetIntentReceiver = new BroadcastReceiver() { @@ -229,15 +229,15 @@ public class MusicService extends MediaBrowserServiceCompat } } }; - private AudioManager audioManager; - private final IntentFilter becomingNoisyReceiverIntentFilter = - new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - private boolean becomingNoisyReceiverRegistered; - private final IntentFilter bluetoothConnectedIntentFilter = - new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); - private boolean bluetoothConnectedRegistered = false; - private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); - private boolean headsetReceiverRegistered = false; + private AudioManager audioManager; + private final IntentFilter becomingNoisyReceiverIntentFilter = + new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + private boolean becomingNoisyReceiverRegistered; + private final IntentFilter bluetoothConnectedIntentFilter = + new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED); + private boolean bluetoothConnectedRegistered = false; + private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + private boolean headsetReceiverRegistered = false; private MediaSessionCompat mediaSession; private ContentObserver mediaStoreObserver; private HandlerThread musicPlayerHandlerThread; @@ -291,59 +291,59 @@ public class MusicService extends MediaBrowserServiceCompat private HandlerThread queueSaveHandlerThread; private boolean queuesRestored; private int repeatMode; - private int shuffleMode; - private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); - private final BroadcastReceiver bluetoothReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) - && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { - if (VERSION.SDK_INT >= VERSION_CODES.M) { - if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { + private int shuffleMode; + private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper(); + private final BroadcastReceiver bluetoothReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(final Context context, final Intent intent) { + String action = intent.getAction(); + if (action != null) { + if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action) + && PreferenceUtil.INSTANCE.isBluetoothSpeaker()) { + if (VERSION.SDK_INT >= VERSION_CODES.M) { + if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) { play(); + } + } else { + if (getAudioManager().isBluetoothA2dpOn()) { + play(); + } + } + } } - } else { - if (getAudioManager().isBluetoothA2dpOn()) { - play(); - } - } } - } - } - }; - private final PhoneStateListener phoneStateListener = - new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - switch (state) { - case TelephonyManager.CALL_STATE_IDLE: - // Not in call: Play music - play(); - break; - case TelephonyManager.CALL_STATE_RINGING: - case TelephonyManager.CALL_STATE_OFFHOOK: - // A call is dialing, active or on hold - pause(); - break; - default: - } - super.onCallStateChanged(state, incomingNumber); - } - }; - private final BroadcastReceiver headsetReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action != null) { - if (Intent.ACTION_HEADSET_PLUG.equals(action)) { - int state = intent.getIntExtra("state", -1); - switch (state) { - case 0: - pause(); + }; + private final PhoneStateListener phoneStateListener = + new PhoneStateListener() { + @Override + public void onCallStateChanged(int state, String incomingNumber) { + switch (state) { + case TelephonyManager.CALL_STATE_IDLE: + // Not in call: Play music + play(); + break; + case TelephonyManager.CALL_STATE_RINGING: + case TelephonyManager.CALL_STATE_OFFHOOK: + // A call is dialing, active or on hold + pause(); + break; + default: + } + super.onCallStateChanged(state, incomingNumber); + } + }; + private final BroadcastReceiver headsetReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action != null) { + if (Intent.ACTION_HEADSET_PLUG.equals(action)) { + int state = intent.getIntExtra("state", -1); + switch (state) { + case 0: + pause(); break; case 1: play(); @@ -1123,7 +1123,7 @@ public class MusicService extends MediaBrowserServiceCompat if (playback instanceof CrossFadePlayer) { ((CrossFadePlayer) playback).sourceChangedByUser(); } - } else { + } else { trackEndedByCrossfade = false; } if (openTrackAndPrepareNextAt(position)) { @@ -1594,15 +1594,18 @@ public class MusicService extends MediaBrowserServiceCompat Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(mediaButtonReceiverComponentName); - PendingIntent mediaButtonReceiverPendingIntent = - PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + PendingIntent mediaButtonReceiverPendingIntent; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, PendingIntent.FLAG_MUTABLE); + } else { + mediaButtonReceiverPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0); + } - mediaSession = - new MediaSessionCompat( - this, - "RetroMusicPlayer", - mediaButtonReceiverComponentName, - mediaButtonReceiverPendingIntent); + mediaSession = new MediaSessionCompat( + this, + "RetroMusicPlayer", + mediaButtonReceiverComponentName, + mediaButtonReceiverPendingIntent); MediaSessionCallback mediasessionCallback = new MediaSessionCallback(getApplicationContext(), this); mediaSession.setFlags( diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt index b188f4b9..511e3659 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationImpl.kt @@ -69,13 +69,21 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent { action.putExtra(MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel) action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP val clickIntent = - PendingIntent.getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PendingIntent.getActivity(service, 0, action, PendingIntent.FLAG_IMMUTABLE) + } else { + PendingIntent.getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT) + } val serviceName = ComponentName(service, MusicService::class.java) val intent = Intent(ACTION_QUIT) intent.component = serviceName - val deleteIntent = PendingIntent.getService(service, 0, intent, 0) - + val deleteIntent = PendingIntent.getService( + service, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) val bigNotificationImageSize = service.resources .getDimensionPixelSize(R.dimen.notification_big_image_size) service.runOnUiThread { @@ -191,6 +199,6 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent { val serviceName = ComponentName(service, MusicService::class.java) val intent = Intent(action) intent.component = serviceName - return PendingIntent.getService(service, 0, intent, 0) + return PendingIntent.getService(service, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt index 7a5ce7c8..201ec963 100644 --- a/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt +++ b/app/src/main/java/code/name/monkey/retromusic/service/notification/PlayingNotificationOreo.kt @@ -26,6 +26,7 @@ import androidx.core.app.NotificationCompat import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor import code.name.monkey.appthemehelper.util.ColorUtil import code.name.monkey.appthemehelper.util.MaterialValueHelper +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.R import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.glide.GlideApp @@ -78,7 +79,12 @@ class PlayingNotificationOreo : PlayingNotification() { action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP val clickIntent = PendingIntent - .getActivity(service, 0, action, PendingIntent.FLAG_UPDATE_CURRENT) + .getActivity( + service, + 0, + action, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) val deleteIntent = buildPendingIntent(service, ACTION_QUIT, null) val builder = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) @@ -143,10 +149,16 @@ class PlayingNotificationOreo : PlayingNotification() { ) } - if (!PreferenceUtil.isColoredNotification) { - bgColorFinal = resolveColor(service, R.attr.colorPrimary, Color.WHITE) + // Android 12 applies a standard Notification template to every notification + // which will in turn have a default background so setting a different background + // than that, looks weird + if (!VersionUtils.hasS()) { + if (!PreferenceUtil.isColoredNotification) { + bgColorFinal = + resolveColor(service, R.attr.colorPrimary, Color.WHITE) + } + setBackgroundColor(bgColorFinal) } - setBackgroundColor(bgColorFinal) setNotificationContent(ColorUtil.isColorLight(bgColorFinal)) if (stopped) { @@ -250,7 +262,7 @@ class PlayingNotificationOreo : PlayingNotification() { ): PendingIntent { val intent = Intent(action) intent.component = serviceName - return PendingIntent.getService(context, 0, intent, 0) + return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt index 9971afb7..d9a66e21 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt +++ b/app/src/main/java/code/name/monkey/retromusic/util/MusicUtil.kt @@ -1,19 +1,23 @@ package code.name.monkey.retromusic.util +import android.app.Activity import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent import android.database.Cursor import android.net.Uri +import android.os.Build import android.os.Environment import android.provider.BaseColumns import android.provider.MediaStore import android.text.TextUtils import android.util.Log import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.core.content.FileProvider import androidx.fragment.app.FragmentActivity +import code.name.monkey.appthemehelper.util.VersionUtils import code.name.monkey.retromusic.R import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.SongEntity @@ -72,15 +76,18 @@ object MusicUtil : KoinComponent { return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1 • $string2" } - fun createAlbumArtFile(): File { + fun createAlbumArtFile(context: Context): File { return File( - createAlbumArtDir(), + createAlbumArtDir(context), System.currentTimeMillis().toString() ) } - private fun createAlbumArtDir(): File { - val albumArtDir = File(Environment.getExternalStorageDirectory(), "/albumthumbs/") + private fun createAlbumArtDir(context: Context): File { + val albumArtDir = File( + if (VersionUtils.hasR()) context.cacheDir else Environment.getExternalStorageDirectory(), + "/albumthumbs/" + ) if (!albumArtDir.exists()) { albumArtDir.mkdirs() try { @@ -517,6 +524,14 @@ object MusicUtil : KoinComponent { } } + @RequiresApi(Build.VERSION_CODES.R) + fun deleteTracksQ(activity: Activity, songs: List) { + val pendingIntent = MediaStore.createDeleteRequest(activity.contentResolver, songs.map { + getSongFileUri(it.id) + }) + activity.startIntentSenderForResult(pendingIntent.intentSender, 45, null, 0, 0, 0, null); + } + fun songByGenre(genreId: Long): Song { return repository.getSongByGenre(genreId) } diff --git a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java index 9b677849..bcdf278e 100644 --- a/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java +++ b/app/src/main/java/code/name/monkey/retromusic/util/PlaylistsUtil.java @@ -36,6 +36,7 @@ import java.util.List; import code.name.monkey.retromusic.R; import code.name.monkey.retromusic.db.PlaylistWithSongs; +import code.name.monkey.retromusic.helper.M3UConstants; import code.name.monkey.retromusic.helper.M3UWriter; import code.name.monkey.retromusic.model.Playlist; import code.name.monkey.retromusic.model.PlaylistSong; @@ -319,9 +320,9 @@ public class PlaylistsUtil { private static boolean doesPlaylistExist( @NonNull Context context, @NonNull final String selection, @NonNull final String[] values) { Cursor cursor = - context - .getContentResolver() - .query(EXTERNAL_CONTENT_URI, new String[] {}, selection, values, null); + context + .getContentResolver() + .query(EXTERNAL_CONTENT_URI, new String[]{}, selection, values, null); boolean exists = false; if (cursor != null) { diff --git a/app/src/main/res/layout-v31/layout_notification_collapsed.xml b/app/src/main/res/layout-v31/layout_notification_collapsed.xml new file mode 100644 index 00000000..ae93ac01 --- /dev/null +++ b/app/src/main/res/layout-v31/layout_notification_collapsed.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v31/layout_notification_expanded.xml b/app/src/main/res/layout-v31/layout_notification_expanded.xml new file mode 100644 index 00000000..acf39d17 --- /dev/null +++ b/app/src/main/res/layout-v31/layout_notification_expanded.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v31/donottranslate.xml b/app/src/main/res/values-v31/donottranslate.xml index e9ef74c3..f4981725 100644 --- a/app/src/main/res/values-v31/donottranslate.xml +++ b/app/src/main/res/values-v31/donottranslate.xml @@ -2,4 +2,6 @@ true true + + false \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 3e5913bc..eff1e1c7 100755 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -26,4 +26,6 @@ Material You false false + + true \ No newline at end of file diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml deleted file mode 100644 index 0f739ff8..00000000 --- a/app/src/main/res/xml/automotive_app_desc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/pref_notification.xml b/app/src/main/res/xml/pref_notification.xml index 77ad1b56..327e9370 100755 --- a/app/src/main/res/xml/pref_notification.xml +++ b/app/src/main/res/xml/pref_notification.xml @@ -13,6 +13,7 @@ android:defaultValue="true" android:key="colored_notification" android:layout="@layout/list_item_view_switch" + app:isPreferenceVisible="@bool/colored_notification_available" android:summary="@string/pref_summary_colored_notification" android:title="@string/pref_title_colored_notification" /> diff --git a/appthemehelper/build.gradle b/appthemehelper/build.gradle index 1f23ddf4..a3c296ca 100644 --- a/appthemehelper/build.gradle +++ b/appthemehelper/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 31 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt index 80a9585e..0822db40 100644 --- a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt @@ -12,14 +12,14 @@ object VersionUtils { * @return true if device is running API >= 21 */ fun hasLollipop(): Boolean { - return Build.VERSION.SDK_INT >= 21 + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP } /** * @return true if device is running API >= 23 */ fun hasMarshmallow(): Boolean { - return Build.VERSION.SDK_INT >= 23 + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M } /** @@ -30,7 +30,7 @@ object VersionUtils { } /** - * @return true if device is running API >= 24 + * @return true if device is running API >= 25 */ fun hasNougatMR(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 @@ -44,20 +44,31 @@ object VersionUtils { } /** - * @return true if device is running API >= 27 + * @return true if device is running API >= 28 */ fun hasP(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P } /** - * @return true if device is running API >= 28 + * @return true if device is running API >= 29 */ @JvmStatic fun hasQ(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q } + /** + * @return true if device is running API >= 30 + */ + @JvmStatic + fun hasR(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R + } + + /** + * @return true if device is running API >= 31 + */ @JvmStatic fun hasS(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S