Merge pull request #1169 from prathameshmm02/dev
Support for scoped storage and better compatibility with Android 12main
commit
b2970a6185
|
@ -9,14 +9,14 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 31
|
||||||
|
|
||||||
renderscriptTargetApi 29//must match target sdk and build tools
|
renderscriptTargetApi 29//must match target sdk and build tools
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
applicationId "code.name.monkey.retromusic"
|
applicationId "code.name.monkey.retromusic"
|
||||||
versionCode 10519
|
versionCode 10537
|
||||||
versionName '5.0.0' + "_" + getDate()
|
versionName '5.2.1 ' + "_" + getDate()
|
||||||
|
|
||||||
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
|
buildConfigField("String", "GOOGLE_PLAY_LICENSING_KEY", "\"${getProperty(getProperties('../public.properties'), 'GOOGLE_PLAY_LICENSE_KEY')}\"")
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
tools:targetApi="m">
|
tools:targetApi="m">
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/SplashTheme">
|
android:theme="@style/SplashTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -144,6 +145,7 @@
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".cast.ExpandedControlsActivity"
|
android:name=".cast.ExpandedControlsActivity"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
|
@ -176,13 +178,17 @@
|
||||||
android:resource="@xml/provider_paths" />
|
android:resource="@xml/provider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver android:name=".service.MediaButtonIntentReceiver">
|
<receiver
|
||||||
|
android:name=".service.MediaButtonIntentReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".appwidgets.BootReceiver">
|
<receiver
|
||||||
|
android:name=".appwidgets.BootReceiver"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||||
|
@ -191,7 +197,7 @@
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".appwidgets.AppWidgetBig"
|
android:name=".appwidgets.AppWidgetBig"
|
||||||
android:exported="false"
|
android:exported="true"
|
||||||
android:label="@string/app_widget_big_name">
|
android:label="@string/app_widget_big_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -204,7 +210,7 @@
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".appwidgets.AppWidgetClassic"
|
android:name=".appwidgets.AppWidgetClassic"
|
||||||
android:exported="false"
|
android:exported="true"
|
||||||
android:label="@string/app_widget_classic_name">
|
android:label="@string/app_widget_classic_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -216,7 +222,7 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".appwidgets.AppWidgetSmall"
|
android:name=".appwidgets.AppWidgetSmall"
|
||||||
android:exported="false"
|
android:exported="true"
|
||||||
android:label="@string/app_widget_small_name">
|
android:label="@string/app_widget_small_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -228,7 +234,7 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".appwidgets.AppWidgetText"
|
android:name=".appwidgets.AppWidgetText"
|
||||||
android:exported="false"
|
android:exported="true"
|
||||||
android:label="@string/app_widget_text_name">
|
android:label="@string/app_widget_text_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -240,7 +246,7 @@
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".appwidgets.AppWidgetCard"
|
android:name=".appwidgets.AppWidgetCard"
|
||||||
android:exported="false"
|
android:exported="true"
|
||||||
android:label="@string/app_widget_card_name">
|
android:label="@string/app_widget_card_name">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
@ -274,17 +280,6 @@
|
||||||
android:name="com.android.vending.splits.required"
|
android:name="com.android.vending.splits.required"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<!-- Android Auto -->
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.car.application"
|
|
||||||
android:resource="@xml/automotive_app_desc" />
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.car.application.theme"
|
|
||||||
android:resource="@style/CarTheme" />
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
|
||||||
android:resource="@drawable/ic_notification" />
|
|
||||||
|
|
||||||
<!-- ChromeCast -->
|
<!-- ChromeCast -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
|
|
|
@ -21,28 +21,36 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.animation.OvershootInterpolator
|
import android.view.animation.OvershootInterpolator
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.IntentSenderRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import code.name.monkey.appthemehelper.ThemeStore
|
import code.name.monkey.appthemehelper.ThemeStore
|
||||||
import code.name.monkey.appthemehelper.util.ATHUtil
|
import code.name.monkey.appthemehelper.util.ATHUtil
|
||||||
import code.name.monkey.appthemehelper.util.TintHelper
|
import code.name.monkey.appthemehelper.util.TintHelper
|
||||||
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.R.drawable
|
import code.name.monkey.retromusic.R.drawable
|
||||||
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
import code.name.monkey.retromusic.activities.base.AbsBaseActivity
|
||||||
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
||||||
import code.name.monkey.retromusic.extensions.accentColor
|
import code.name.monkey.retromusic.extensions.accentColor
|
||||||
import code.name.monkey.retromusic.model.ArtworkInfo
|
import code.name.monkey.retromusic.model.ArtworkInfo
|
||||||
import code.name.monkey.retromusic.model.LoadingInfo
|
import code.name.monkey.retromusic.model.AudioTagInfo
|
||||||
import code.name.monkey.retromusic.repository.Repository
|
import code.name.monkey.retromusic.repository.Repository
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
import code.name.monkey.retromusic.util.SAFUtil
|
import code.name.monkey.retromusic.util.SAFUtil
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.jaudiotagger.audio.AudioFile
|
import org.jaudiotagger.audio.AudioFile
|
||||||
import org.jaudiotagger.audio.AudioFileIO
|
import org.jaudiotagger.audio.AudioFileIO
|
||||||
import org.jaudiotagger.tag.FieldKey
|
import org.jaudiotagger.tag.FieldKey
|
||||||
|
@ -66,9 +74,12 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
private var savedArtworkInfo: ArtworkInfo? = null
|
private var savedArtworkInfo: ArtworkInfo? = null
|
||||||
private var _binding: VB? = null
|
private var _binding: VB? = null
|
||||||
protected val binding: VB get() = _binding!!
|
protected val binding: VB get() = _binding!!
|
||||||
|
private var cacheFiles = listOf<File>()
|
||||||
|
|
||||||
abstract val bindingInflater: (LayoutInflater) -> VB
|
abstract val bindingInflater: (LayoutInflater) -> VB
|
||||||
|
|
||||||
|
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
|
||||||
|
|
||||||
protected abstract fun loadImageFromFile(selectedFile: Uri?)
|
protected abstract fun loadImageFromFile(selectedFile: Uri?)
|
||||||
|
|
||||||
protected val show: AlertDialog
|
protected val show: AlertDialog
|
||||||
|
@ -195,7 +206,6 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
_binding = bindingInflater.invoke(layoutInflater)
|
_binding = bindingInflater.invoke(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
setStatusbarColorAuto()
|
|
||||||
setTaskDescriptionColorAuto()
|
setTaskDescriptionColorAuto()
|
||||||
|
|
||||||
saveFab = findViewById(R.id.saveTags)
|
saveFab = findViewById(R.id.saveTags)
|
||||||
|
@ -207,6 +217,11 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
setUpViews()
|
setUpViews()
|
||||||
|
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
writeToFiles(getSongUris(), cacheFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpViews() {
|
private fun setUpViews() {
|
||||||
|
@ -265,6 +280,8 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
|
|
||||||
protected abstract fun getSongPaths(): List<String>
|
protected abstract fun getSongPaths(): List<String>
|
||||||
|
|
||||||
|
protected abstract fun getSongUris(): List<Uri>
|
||||||
|
|
||||||
protected fun searchWebFor(vararg keys: String) {
|
protected fun searchWebFor(vararg keys: String) {
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
for (key in keys) {
|
for (key in keys) {
|
||||||
|
@ -336,23 +353,53 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
|
|
||||||
hideFab()
|
hideFab()
|
||||||
println(fieldKeyValueMap)
|
println(fieldKeyValueMap)
|
||||||
WriteTagsAsyncTask(this).execute(
|
GlobalScope.launch {
|
||||||
LoadingInfo(
|
if (VersionUtils.hasR()) {
|
||||||
songPaths,
|
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||||
fieldKeyValueMap,
|
this@AbsTagEditorActivity, AudioTagInfo(
|
||||||
artworkInfo
|
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<String>?) {
|
private fun writeTags(paths: List<String>?) {
|
||||||
WriteTagsAsyncTask(this).execute(
|
GlobalScope.launch {
|
||||||
LoadingInfo(
|
if (VersionUtils.hasR()) {
|
||||||
paths,
|
cacheFiles = TagWriter.writeTagsToFilesR(
|
||||||
savedTags,
|
this@AbsTagEditorActivity, AudioTagInfo(
|
||||||
savedArtworkInfo
|
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<VB : ViewBinding> : AbsBaseActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) {
|
||||||
|
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 {
|
companion object {
|
||||||
|
|
||||||
const val EXTRA_ID = "extra_id"
|
const val EXTRA_ID = "extra_id"
|
||||||
const val EXTRA_PALETTE = "extra_palette"
|
const val EXTRA_PALETTE = "extra_palette"
|
||||||
private val TAG = AbsTagEditorActivity::class.java.simpleName
|
private val TAG = AbsTagEditorActivity::class.java.simpleName
|
||||||
|
|
|
@ -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.ArtworkInfo
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import code.name.monkey.retromusic.util.ImageUtil
|
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.generatePalette
|
||||||
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
|
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
@ -193,6 +194,11 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
|
||||||
.map(Song::data)
|
.map(Song::data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map {
|
||||||
|
MusicUtil.getSongFileUri(it.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
|
||||||
import code.name.monkey.retromusic.extensions.appHandleColor
|
import code.name.monkey.retromusic.extensions.appHandleColor
|
||||||
import code.name.monkey.retromusic.extensions.setTint
|
import code.name.monkey.retromusic.extensions.setTint
|
||||||
import code.name.monkey.retromusic.repository.SongRepository
|
import code.name.monkey.retromusic.repository.SongRepository
|
||||||
|
import code.name.monkey.retromusic.util.MusicUtil
|
||||||
import org.jaudiotagger.tag.FieldKey
|
import org.jaudiotagger.tag.FieldKey
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -111,6 +112,8 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
|
||||||
|
|
||||||
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
|
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
|
||||||
|
|
||||||
|
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
|
||||||
|
|
||||||
override fun loadImageFromFile(selectedFile: Uri?) {
|
override fun loadImageFromFile(selectedFile: Uri?) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<String?>?) {
|
||||||
|
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<File>()
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<LoadingInfo, Integer, List<String>> {
|
|
||||||
|
|
||||||
public WriteTagsAsyncTask(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<String> 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<FieldKey, String> 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<String> toBeScanned) {
|
|
||||||
super.onPostExecute(toBeScanned);
|
|
||||||
scan(toBeScanned);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCancelled(List<String> toBeScanned) {
|
|
||||||
super.onCancelled(toBeScanned);
|
|
||||||
scan(toBeScanned);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scan(List<String> 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]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -74,7 +74,7 @@ class AppWidgetBig : BaseAppWidget() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
appWidgetView.setImageViewBitmap(
|
appWidgetView.setImageViewBitmap(
|
||||||
R.id.button_toggle_play_pause, BaseAppWidget.Companion.createBitmap(
|
R.id.button_toggle_play_pause, BaseAppWidget.createBitmap(
|
||||||
RetroUtil.getTintedVectorDrawable(
|
RetroUtil.getTintedVectorDrawable(
|
||||||
context,
|
context,
|
||||||
R.drawable.ic_play_arrow_white_32dp,
|
R.drawable.ic_play_arrow_white_32dp,
|
||||||
|
@ -202,13 +202,13 @@ class AppWidgetBig : BaseAppWidget() {
|
||||||
MainActivity.EXPAND_PANEL,
|
MainActivity.EXPAND_PANEL,
|
||||||
PreferenceUtil.isExpandPanel
|
PreferenceUtil.isExpandPanel
|
||||||
)
|
)
|
||||||
var pendingIntent: PendingIntent
|
|
||||||
|
|
||||||
val serviceName = ComponentName(context, MusicService::class.java)
|
val serviceName = ComponentName(context, MusicService::class.java)
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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)
|
views.setOnClickPendingIntent(R.id.clickable_area, pendingIntent)
|
||||||
|
|
||||||
// Previous track
|
// Previous track
|
||||||
|
|
|
@ -141,11 +141,11 @@ class AppWidgetCard : BaseAppWidget() {
|
||||||
|
|
||||||
if (imageSize == 0) {
|
if (imageSize == 0) {
|
||||||
imageSize =
|
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) {
|
if (cardRadius == 0f) {
|
||||||
cardRadius =
|
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
|
// Load the album cover async and push the update on completion
|
||||||
|
@ -225,13 +225,13 @@ class AppWidgetCard : BaseAppWidget() {
|
||||||
MainActivity.EXPAND_PANEL,
|
MainActivity.EXPAND_PANEL,
|
||||||
PreferenceUtil.isExpandPanel
|
PreferenceUtil.isExpandPanel
|
||||||
)
|
)
|
||||||
var pendingIntent: PendingIntent
|
|
||||||
|
|
||||||
val serviceName = ComponentName(context, MusicService::class.java)
|
val serviceName = ComponentName(context, MusicService::class.java)
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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.image, pendingIntent)
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
|
|
|
@ -216,13 +216,12 @@ class AppWidgetClassic : BaseAppWidget() {
|
||||||
MainActivity.EXPAND_PANEL,
|
MainActivity.EXPAND_PANEL,
|
||||||
PreferenceUtil.isExpandPanel
|
PreferenceUtil.isExpandPanel
|
||||||
)
|
)
|
||||||
var pendingIntent: PendingIntent
|
|
||||||
|
|
||||||
val serviceName = ComponentName(context, MusicService::class.java)
|
val serviceName = ComponentName(context, MusicService::class.java)
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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.image, pendingIntent)
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
|
|
|
@ -201,13 +201,13 @@ class AppWidgetSmall : BaseAppWidget() {
|
||||||
MainActivity.EXPAND_PANEL,
|
MainActivity.EXPAND_PANEL,
|
||||||
PreferenceUtil.isExpandPanel
|
PreferenceUtil.isExpandPanel
|
||||||
)
|
)
|
||||||
var pendingIntent: PendingIntent
|
|
||||||
|
|
||||||
val serviceName = ComponentName(context, MusicService::class.java)
|
val serviceName = ComponentName(context, MusicService::class.java)
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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.image, pendingIntent)
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import android.app.PendingIntent
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
@ -83,13 +84,12 @@ class AppWidgetText : BaseAppWidget() {
|
||||||
MainActivity.EXPAND_PANEL,
|
MainActivity.EXPAND_PANEL,
|
||||||
PreferenceUtil.isExpandPanel
|
PreferenceUtil.isExpandPanel
|
||||||
)
|
)
|
||||||
var pendingIntent: PendingIntent
|
|
||||||
|
|
||||||
val serviceName = ComponentName(context, MusicService::class.java)
|
val serviceName = ComponentName(context, MusicService::class.java)
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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.image, pendingIntent)
|
||||||
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
views.setOnClickPendingIntent(R.id.media_titles, pendingIntent)
|
||||||
|
|
||||||
|
|
|
@ -97,9 +97,9 @@ abstract class BaseAppWidget : AppWidgetProvider() {
|
||||||
val intent = Intent(action)
|
val intent = Intent(action)
|
||||||
intent.component = serviceName
|
intent.component = serviceName
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
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 {
|
} else {
|
||||||
PendingIntent.getService(context, 0, intent, 0)
|
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.os.Bundle
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||||
import code.name.monkey.retromusic.EXTRA_SONG
|
import code.name.monkey.retromusic.EXTRA_SONG
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
|
||||||
|
@ -89,7 +90,11 @@ class DeleteSongsDialog : DialogFragment() {
|
||||||
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
|
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) {
|
||||||
MusicPlayerRemote.playNextSong()
|
MusicPlayerRemote.playNextSong()
|
||||||
}
|
}
|
||||||
if (!SAFUtil.isSAFRequiredForSongs(songs)) {
|
if (VersionUtils.hasQ()) {
|
||||||
|
dismiss()
|
||||||
|
MusicUtil.deleteTracksQ(requireActivity(), songs)
|
||||||
|
reloadTabs()
|
||||||
|
} else if (!SAFUtil.isSAFRequiredForSongs(songs)) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
dismiss()
|
dismiss()
|
||||||
MusicUtil.deleteTracks(requireContext(), songs)
|
MusicUtil.deleteTracks(requireContext(), songs)
|
||||||
|
|
|
@ -21,13 +21,16 @@ import android.widget.Toast
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||||
import code.name.monkey.retromusic.App
|
import code.name.monkey.retromusic.App
|
||||||
import code.name.monkey.retromusic.EXTRA_PLAYLIST
|
import code.name.monkey.retromusic.EXTRA_PLAYLIST
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.db.PlaylistWithSongs
|
import code.name.monkey.retromusic.db.PlaylistWithSongs
|
||||||
import code.name.monkey.retromusic.extensions.colorButtons
|
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.extraNotNull
|
||||||
import code.name.monkey.retromusic.extensions.materialDialog
|
import code.name.monkey.retromusic.extensions.materialDialog
|
||||||
|
import code.name.monkey.retromusic.helper.M3UWriter
|
||||||
import code.name.monkey.retromusic.util.PlaylistsUtil
|
import code.name.monkey.retromusic.util.PlaylistsUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -46,22 +49,59 @@ class SavePlaylistDialog : DialogFragment() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
val playlistWithSongs = extraNotNull<PlaylistWithSongs>(EXTRA_PLAYLIST).value
|
||||||
val playlistWithSongs = extraNotNull<PlaylistWithSongs>(EXTRA_PLAYLIST).value
|
|
||||||
val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs)
|
if (VersionUtils.hasR()) {
|
||||||
MediaScannerConnection.scanFile(
|
createNewFile(
|
||||||
requireActivity(),
|
"audio/mpegurl",
|
||||||
arrayOf<String>(file.path),
|
playlistWithSongs.playlistEntity.playlistName
|
||||||
null
|
) { 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) {
|
} else {
|
||||||
Toast.makeText(
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
requireContext(),
|
val file = PlaylistsUtil.savePlaylistWithSongs(playlistWithSongs)
|
||||||
String.format(App.getContext().getString(R.string.saved_playlist_to), file),
|
MediaScannerConnection.scanFile(
|
||||||
Toast.LENGTH_LONG
|
requireActivity(),
|
||||||
).show()
|
arrayOf<String>(file.path),
|
||||||
dismiss()
|
null
|
||||||
|
) { _, _ ->
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
requireContext(),
|
||||||
|
String.format(App.getContext().getString(R.string.saved_playlist_to), file),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -14,19 +14,22 @@
|
||||||
*/
|
*/
|
||||||
package code.name.monkey.retromusic.fragments.other
|
package code.name.monkey.retromusic.fragments.other
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.transition.Fade
|
import androidx.transition.Fade
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
|
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.R
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
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.FragmentLyricsBinding
|
||||||
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
|
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
|
||||||
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
|
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.MusicPlayerRemote
|
||||||
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
|
||||||
import code.name.monkey.retromusic.lyrics.LrcView
|
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.model.Song
|
||||||
import code.name.monkey.retromusic.util.LyricUtil
|
import code.name.monkey.retromusic.util.LyricUtil
|
||||||
import code.name.monkey.retromusic.util.RetroUtil
|
import code.name.monkey.retromusic.util.RetroUtil
|
||||||
|
@ -107,6 +110,9 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
|
||||||
setupViews()
|
setupViews()
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
updateTitleSong()
|
updateTitleSong()
|
||||||
|
if (VersionUtils.hasR()) {
|
||||||
|
binding.editButton.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupViews() {
|
private fun setupViews() {
|
||||||
|
@ -187,6 +193,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
private fun editNormalLyrics() {
|
private fun editNormalLyrics() {
|
||||||
var content = ""
|
var content = ""
|
||||||
val file = File(MusicPlayerRemote.currentSong.data)
|
val file = File(MusicPlayerRemote.currentSong.data)
|
||||||
|
@ -205,11 +212,13 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
|
||||||
) { _, input ->
|
) { _, input ->
|
||||||
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
|
||||||
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
|
fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
|
||||||
WriteTagsAsyncTask(requireActivity()).execute(
|
GlobalScope.launch {
|
||||||
LoadingInfo(
|
TagWriter.writeTagsToFiles(
|
||||||
listOf(song.data), fieldKeyValueMap, null
|
requireContext(), AudioTagInfo(
|
||||||
|
listOf(song.data), fieldKeyValueMap, null
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
positiveButton(res = R.string.save) {
|
positiveButton(res = R.string.save) {
|
||||||
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
|
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
|
||||||
|
@ -219,6 +228,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
private fun editSyncedLyrics() {
|
private fun editSyncedLyrics() {
|
||||||
var lrcFile: File? = null
|
var lrcFile: File? = null
|
||||||
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
|
if (LyricUtil.isLrcOriginalFileExist(song.data)) {
|
||||||
|
|
|
@ -18,10 +18,7 @@ import code.name.monkey.retromusic.db.PlaylistWithSongs
|
||||||
import code.name.monkey.retromusic.db.toSongs
|
import code.name.monkey.retromusic.db.toSongs
|
||||||
import code.name.monkey.retromusic.model.Playlist
|
import code.name.monkey.retromusic.model.Playlist
|
||||||
import code.name.monkey.retromusic.model.Song
|
import code.name.monkey.retromusic.model.Song
|
||||||
import java.io.BufferedWriter
|
import java.io.*
|
||||||
import java.io.File
|
|
||||||
import java.io.FileWriter
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
object M3UWriter : M3UConstants {
|
object M3UWriter : M3UConstants {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -69,4 +66,23 @@ object M3UWriter : M3UConstants {
|
||||||
}
|
}
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun writeIO(outputStream: OutputStream, playlistWithSongs: PlaylistWithSongs) {
|
||||||
|
val songs: List<Song> = 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package code.name.monkey.retromusic.model
|
||||||
|
|
||||||
import org.jaudiotagger.tag.FieldKey
|
import org.jaudiotagger.tag.FieldKey
|
||||||
|
|
||||||
class LoadingInfo(
|
class AudioTagInfo(
|
||||||
val filePaths: List<String>?,
|
val filePaths: List<String>?,
|
||||||
val fieldKeyValueMap: Map<FieldKey, String>?,
|
val fieldKeyValueMap: Map<FieldKey, String>?,
|
||||||
val artworkInfo: ArtworkInfo?
|
val artworkInfo: ArtworkInfo?
|
|
@ -189,13 +189,13 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
|
|
||||||
private final AppWidgetBig appWidgetBig = AppWidgetBig.Companion.getInstance();
|
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 =
|
private final BroadcastReceiver widgetIntentReceiver =
|
||||||
new BroadcastReceiver() {
|
new BroadcastReceiver() {
|
||||||
|
@ -229,15 +229,15 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private AudioManager audioManager;
|
private AudioManager audioManager;
|
||||||
private final IntentFilter becomingNoisyReceiverIntentFilter =
|
private final IntentFilter becomingNoisyReceiverIntentFilter =
|
||||||
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||||
private boolean becomingNoisyReceiverRegistered;
|
private boolean becomingNoisyReceiverRegistered;
|
||||||
private final IntentFilter bluetoothConnectedIntentFilter =
|
private final IntentFilter bluetoothConnectedIntentFilter =
|
||||||
new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
|
new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
|
||||||
private boolean bluetoothConnectedRegistered = false;
|
private boolean bluetoothConnectedRegistered = false;
|
||||||
private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
private final IntentFilter headsetReceiverIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
|
||||||
private boolean headsetReceiverRegistered = false;
|
private boolean headsetReceiverRegistered = false;
|
||||||
private MediaSessionCompat mediaSession;
|
private MediaSessionCompat mediaSession;
|
||||||
private ContentObserver mediaStoreObserver;
|
private ContentObserver mediaStoreObserver;
|
||||||
private HandlerThread musicPlayerHandlerThread;
|
private HandlerThread musicPlayerHandlerThread;
|
||||||
|
@ -291,59 +291,59 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
private HandlerThread queueSaveHandlerThread;
|
private HandlerThread queueSaveHandlerThread;
|
||||||
private boolean queuesRestored;
|
private boolean queuesRestored;
|
||||||
private int repeatMode;
|
private int repeatMode;
|
||||||
private int shuffleMode;
|
private int shuffleMode;
|
||||||
private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper();
|
private final SongPlayCountHelper songPlayCountHelper = new SongPlayCountHelper();
|
||||||
private final BroadcastReceiver bluetoothReceiver =
|
private final BroadcastReceiver bluetoothReceiver =
|
||||||
new BroadcastReceiver() {
|
new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(final Context context, final Intent intent) {
|
public void onReceive(final Context context, final Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)
|
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)
|
||||||
&& PreferenceUtil.INSTANCE.isBluetoothSpeaker()) {
|
&& PreferenceUtil.INSTANCE.isBluetoothSpeaker()) {
|
||||||
if (VERSION.SDK_INT >= VERSION_CODES.M) {
|
if (VERSION.SDK_INT >= VERSION_CODES.M) {
|
||||||
if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) {
|
if (getAudioManager().getDevices(AudioManager.GET_DEVICES_OUTPUTS).length > 0) {
|
||||||
play();
|
play();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (getAudioManager().isBluetoothA2dpOn()) {
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (getAudioManager().isBluetoothA2dpOn()) {
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
private final PhoneStateListener phoneStateListener =
|
||||||
};
|
new PhoneStateListener() {
|
||||||
private final PhoneStateListener phoneStateListener =
|
@Override
|
||||||
new PhoneStateListener() {
|
public void onCallStateChanged(int state, String incomingNumber) {
|
||||||
@Override
|
switch (state) {
|
||||||
public void onCallStateChanged(int state, String incomingNumber) {
|
case TelephonyManager.CALL_STATE_IDLE:
|
||||||
switch (state) {
|
// Not in call: Play music
|
||||||
case TelephonyManager.CALL_STATE_IDLE:
|
play();
|
||||||
// Not in call: Play music
|
break;
|
||||||
play();
|
case TelephonyManager.CALL_STATE_RINGING:
|
||||||
break;
|
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||||
case TelephonyManager.CALL_STATE_RINGING:
|
// A call is dialing, active or on hold
|
||||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
pause();
|
||||||
// A call is dialing, active or on hold
|
break;
|
||||||
pause();
|
default:
|
||||||
break;
|
}
|
||||||
default:
|
super.onCallStateChanged(state, incomingNumber);
|
||||||
}
|
}
|
||||||
super.onCallStateChanged(state, incomingNumber);
|
};
|
||||||
}
|
private final BroadcastReceiver headsetReceiver =
|
||||||
};
|
new BroadcastReceiver() {
|
||||||
private final BroadcastReceiver headsetReceiver =
|
@Override
|
||||||
new BroadcastReceiver() {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@Override
|
String action = intent.getAction();
|
||||||
public void onReceive(Context context, Intent intent) {
|
if (action != null) {
|
||||||
String action = intent.getAction();
|
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
|
||||||
if (action != null) {
|
int state = intent.getIntExtra("state", -1);
|
||||||
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
|
switch (state) {
|
||||||
int state = intent.getIntExtra("state", -1);
|
case 0:
|
||||||
switch (state) {
|
pause();
|
||||||
case 0:
|
|
||||||
pause();
|
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
play();
|
play();
|
||||||
|
@ -1123,7 +1123,7 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
if (playback instanceof CrossFadePlayer) {
|
if (playback instanceof CrossFadePlayer) {
|
||||||
((CrossFadePlayer) playback).sourceChangedByUser();
|
((CrossFadePlayer) playback).sourceChangedByUser();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trackEndedByCrossfade = false;
|
trackEndedByCrossfade = false;
|
||||||
}
|
}
|
||||||
if (openTrackAndPrepareNextAt(position)) {
|
if (openTrackAndPrepareNextAt(position)) {
|
||||||
|
@ -1594,15 +1594,18 @@ public class MusicService extends MediaBrowserServiceCompat
|
||||||
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
|
||||||
mediaButtonIntent.setComponent(mediaButtonReceiverComponentName);
|
mediaButtonIntent.setComponent(mediaButtonReceiverComponentName);
|
||||||
|
|
||||||
PendingIntent mediaButtonReceiverPendingIntent =
|
PendingIntent mediaButtonReceiverPendingIntent;
|
||||||
PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
|
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 =
|
mediaSession = new MediaSessionCompat(
|
||||||
new MediaSessionCompat(
|
this,
|
||||||
this,
|
"RetroMusicPlayer",
|
||||||
"RetroMusicPlayer",
|
mediaButtonReceiverComponentName,
|
||||||
mediaButtonReceiverComponentName,
|
mediaButtonReceiverPendingIntent);
|
||||||
mediaButtonReceiverPendingIntent);
|
|
||||||
MediaSessionCallback mediasessionCallback =
|
MediaSessionCallback mediasessionCallback =
|
||||||
new MediaSessionCallback(getApplicationContext(), this);
|
new MediaSessionCallback(getApplicationContext(), this);
|
||||||
mediaSession.setFlags(
|
mediaSession.setFlags(
|
||||||
|
|
|
@ -69,13 +69,21 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent {
|
||||||
action.putExtra(MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel)
|
action.putExtra(MainActivity.EXPAND_PANEL, PreferenceUtil.isExpandPanel)
|
||||||
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
val clickIntent =
|
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 serviceName = ComponentName(service, MusicService::class.java)
|
||||||
val intent = Intent(ACTION_QUIT)
|
val intent = Intent(ACTION_QUIT)
|
||||||
intent.component = serviceName
|
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
|
val bigNotificationImageSize = service.resources
|
||||||
.getDimensionPixelSize(R.dimen.notification_big_image_size)
|
.getDimensionPixelSize(R.dimen.notification_big_image_size)
|
||||||
service.runOnUiThread {
|
service.runOnUiThread {
|
||||||
|
@ -191,6 +199,6 @@ class PlayingNotificationImpl : PlayingNotification(), KoinComponent {
|
||||||
val serviceName = ComponentName(service, MusicService::class.java)
|
val serviceName = ComponentName(service, MusicService::class.java)
|
||||||
val intent = Intent(action)
|
val intent = Intent(action)
|
||||||
intent.component = serviceName
|
intent.component = serviceName
|
||||||
return PendingIntent.getService(service, 0, intent, 0)
|
return PendingIntent.getService(service, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,6 +26,7 @@ import androidx.core.app.NotificationCompat
|
||||||
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
|
import code.name.monkey.appthemehelper.util.ATHUtil.resolveColor
|
||||||
import code.name.monkey.appthemehelper.util.ColorUtil
|
import code.name.monkey.appthemehelper.util.ColorUtil
|
||||||
import code.name.monkey.appthemehelper.util.MaterialValueHelper
|
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.R
|
||||||
import code.name.monkey.retromusic.activities.MainActivity
|
import code.name.monkey.retromusic.activities.MainActivity
|
||||||
import code.name.monkey.retromusic.glide.GlideApp
|
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
|
action.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
|
||||||
val clickIntent = PendingIntent
|
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 deleteIntent = buildPendingIntent(service, ACTION_QUIT, null)
|
||||||
|
|
||||||
val builder = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
val builder = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
|
||||||
|
@ -143,10 +149,16 @@ class PlayingNotificationOreo : PlayingNotification() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PreferenceUtil.isColoredNotification) {
|
// Android 12 applies a standard Notification template to every notification
|
||||||
bgColorFinal = resolveColor(service, R.attr.colorPrimary, Color.WHITE)
|
// 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))
|
setNotificationContent(ColorUtil.isColorLight(bgColorFinal))
|
||||||
|
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
|
@ -250,7 +262,7 @@ class PlayingNotificationOreo : PlayingNotification() {
|
||||||
): PendingIntent {
|
): PendingIntent {
|
||||||
val intent = Intent(action)
|
val intent = Intent(action)
|
||||||
intent.component = serviceName
|
intent.component = serviceName
|
||||||
return PendingIntent.getService(context, 0, intent, 0)
|
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
package code.name.monkey.retromusic.util
|
package code.name.monkey.retromusic.util
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.ContentUris
|
import android.content.ContentUris
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.provider.BaseColumns
|
import android.provider.BaseColumns
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
||||||
import code.name.monkey.retromusic.R
|
import code.name.monkey.retromusic.R
|
||||||
import code.name.monkey.retromusic.db.PlaylistEntity
|
import code.name.monkey.retromusic.db.PlaylistEntity
|
||||||
import code.name.monkey.retromusic.db.SongEntity
|
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"
|
return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1 • $string2"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createAlbumArtFile(): File {
|
fun createAlbumArtFile(context: Context): File {
|
||||||
return File(
|
return File(
|
||||||
createAlbumArtDir(),
|
createAlbumArtDir(context),
|
||||||
System.currentTimeMillis().toString()
|
System.currentTimeMillis().toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAlbumArtDir(): File {
|
private fun createAlbumArtDir(context: Context): File {
|
||||||
val albumArtDir = File(Environment.getExternalStorageDirectory(), "/albumthumbs/")
|
val albumArtDir = File(
|
||||||
|
if (VersionUtils.hasR()) context.cacheDir else Environment.getExternalStorageDirectory(),
|
||||||
|
"/albumthumbs/"
|
||||||
|
)
|
||||||
if (!albumArtDir.exists()) {
|
if (!albumArtDir.exists()) {
|
||||||
albumArtDir.mkdirs()
|
albumArtDir.mkdirs()
|
||||||
try {
|
try {
|
||||||
|
@ -517,6 +524,14 @@ object MusicUtil : KoinComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
fun deleteTracksQ(activity: Activity, songs: List<Song>) {
|
||||||
|
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 {
|
fun songByGenre(genreId: Long): Song {
|
||||||
return repository.getSongByGenre(genreId)
|
return repository.getSongByGenre(genreId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.util.List;
|
||||||
|
|
||||||
import code.name.monkey.retromusic.R;
|
import code.name.monkey.retromusic.R;
|
||||||
import code.name.monkey.retromusic.db.PlaylistWithSongs;
|
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.helper.M3UWriter;
|
||||||
import code.name.monkey.retromusic.model.Playlist;
|
import code.name.monkey.retromusic.model.Playlist;
|
||||||
import code.name.monkey.retromusic.model.PlaylistSong;
|
import code.name.monkey.retromusic.model.PlaylistSong;
|
||||||
|
@ -319,9 +320,9 @@ public class PlaylistsUtil {
|
||||||
private static boolean doesPlaylistExist(
|
private static boolean doesPlaylistExist(
|
||||||
@NonNull Context context, @NonNull final String selection, @NonNull final String[] values) {
|
@NonNull Context context, @NonNull final String selection, @NonNull final String[] values) {
|
||||||
Cursor cursor =
|
Cursor cursor =
|
||||||
context
|
context
|
||||||
.getContentResolver()
|
.getContentResolver()
|
||||||
.query(EXTERNAL_CONTENT_URI, new String[] {}, selection, values, null);
|
.query(EXTERNAL_CONTENT_URI, new String[]{}, selection, values, null);
|
||||||
|
|
||||||
boolean exists = false;
|
boolean exists = false;
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:background="@color/md_red_500"
|
||||||
|
tools:ignore="ContentDescription">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignBottom="@+id/content"
|
||||||
|
android:layout_alignParentEnd="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/largeIcon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_toStartOf="@id/actions"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/Theme.RetroMusic.Notification"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/Theme.RetroMusic.Notification.Title"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/actions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@id/content"
|
||||||
|
android:layout_alignBottom="@id/content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:layout_marginEnd="48dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_prev"
|
||||||
|
android:layout_width="38dp"
|
||||||
|
android:layout_height="38dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_skip_previous_round_white_32dp"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_play_pause"
|
||||||
|
android:layout_width="38dp"
|
||||||
|
android:layout_height="38dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_pause"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_next"
|
||||||
|
android:layout_width="38dp"
|
||||||
|
android:layout_height="38dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_skip_next_round_white_32dp"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:background="@color/md_red_500">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBottom="@+id/content"
|
||||||
|
android:layout_alignParentEnd="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/largeIcon"
|
||||||
|
android:layout_width="@dimen/notification_big_image_size"
|
||||||
|
android:layout_height="@dimen/notification_big_image_size"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="144dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/Theme.RetroMusic.Notification"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:lines="1"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/Theme.RetroMusic.Notification.Title"
|
||||||
|
tools:text="@tools:sample/lorem/random" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="0dp">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_prev"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:src="@drawable/ic_skip_previous_round_white_32dp"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_play_pause"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:src="@drawable/ic_pause_white_48dp"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_next"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:src="@drawable/ic_skip_next_round_white_32dp"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_quit"
|
||||||
|
android:layout_width="42dp"
|
||||||
|
android:layout_height="42dp"
|
||||||
|
android:background="@drawable/notification_selector"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
tools:src="@drawable/ic_close"
|
||||||
|
tools:tint="?colorOnPrimary" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
|
@ -2,4 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<bool name="md3_available">true</bool>
|
<bool name="md3_available">true</bool>
|
||||||
<bool name="md3_enabled">true</bool>
|
<bool name="md3_enabled">true</bool>
|
||||||
|
|
||||||
|
<bool name="colored_notification_available">false</bool>
|
||||||
</resources>
|
</resources>
|
|
@ -26,4 +26,6 @@
|
||||||
<string name="pref_title_md3">Material You</string>
|
<string name="pref_title_md3">Material You</string>
|
||||||
<bool name="md3_available">false</bool>
|
<bool name="md3_available">false</bool>
|
||||||
<bool name="md3_enabled">false</bool>
|
<bool name="md3_enabled">false</bool>
|
||||||
|
|
||||||
|
<bool name="colored_notification_available">true</bool>
|
||||||
</resources>
|
</resources>
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<automotiveApp>
|
|
||||||
<uses name="media" />
|
|
||||||
</automotiveApp>
|
|
|
@ -13,6 +13,7 @@
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="colored_notification"
|
android:key="colored_notification"
|
||||||
android:layout="@layout/list_item_view_switch"
|
android:layout="@layout/list_item_view_switch"
|
||||||
|
app:isPreferenceVisible="@bool/colored_notification_available"
|
||||||
android:summary="@string/pref_summary_colored_notification"
|
android:summary="@string/pref_summary_colored_notification"
|
||||||
android:title="@string/pref_title_colored_notification" />
|
android:title="@string/pref_title_colored_notification" />
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 31
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 31
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,14 @@ object VersionUtils {
|
||||||
* @return true if device is running API >= 21
|
* @return true if device is running API >= 21
|
||||||
*/
|
*/
|
||||||
fun hasLollipop(): Boolean {
|
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
|
* @return true if device is running API >= 23
|
||||||
*/
|
*/
|
||||||
fun hasMarshmallow(): Boolean {
|
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 {
|
fun hasNougatMR(): Boolean {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1
|
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 {
|
fun hasP(): Boolean {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
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
|
@JvmStatic
|
||||||
fun hasQ(): Boolean {
|
fun hasQ(): Boolean {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
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
|
@JvmStatic
|
||||||
fun hasS(): Boolean {
|
fun hasS(): Boolean {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
Loading…
Reference in New Issue