2020-08-13 17:08:37 +00:00
|
|
|
package code.name.monkey.retromusic.util
|
|
|
|
|
2021-07-25 05:10:13 +00:00
|
|
|
import android.app.Activity
|
2020-08-13 17:08:37 +00:00
|
|
|
import android.content.ContentUris
|
|
|
|
import android.content.ContentValues
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
2020-09-09 12:37:25 +00:00
|
|
|
import android.database.Cursor
|
2020-08-13 17:08:37 +00:00
|
|
|
import android.net.Uri
|
2021-07-25 05:10:13 +00:00
|
|
|
import android.os.Build
|
2020-08-13 17:08:37 +00:00
|
|
|
import android.os.Environment
|
|
|
|
import android.provider.BaseColumns
|
|
|
|
import android.provider.MediaStore
|
|
|
|
import android.text.TextUtils
|
2020-09-09 12:37:25 +00:00
|
|
|
import android.util.Log
|
2020-08-13 17:08:37 +00:00
|
|
|
import android.widget.Toast
|
2021-07-25 05:10:13 +00:00
|
|
|
import androidx.annotation.RequiresApi
|
2020-08-13 17:08:37 +00:00
|
|
|
import androidx.core.content.FileProvider
|
|
|
|
import androidx.fragment.app.FragmentActivity
|
2021-11-22 10:01:33 +00:00
|
|
|
import code.name.monkey.appthemehelper.util.VersionUtils
|
2020-08-13 17:08:37 +00:00
|
|
|
import code.name.monkey.retromusic.R
|
2020-10-17 09:17:57 +00:00
|
|
|
import code.name.monkey.retromusic.db.PlaylistEntity
|
2020-08-20 06:49:08 +00:00
|
|
|
import code.name.monkey.retromusic.db.SongEntity
|
2020-10-17 09:17:57 +00:00
|
|
|
import code.name.monkey.retromusic.db.toSongEntity
|
2020-09-17 21:25:41 +00:00
|
|
|
import code.name.monkey.retromusic.extensions.getLong
|
2020-08-13 17:08:37 +00:00
|
|
|
import code.name.monkey.retromusic.helper.MusicPlayerRemote.removeFromQueue
|
|
|
|
import code.name.monkey.retromusic.model.Artist
|
|
|
|
import code.name.monkey.retromusic.model.Playlist
|
|
|
|
import code.name.monkey.retromusic.model.Song
|
|
|
|
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
|
|
|
|
import code.name.monkey.retromusic.repository.RealPlaylistRepository
|
2020-10-17 09:17:57 +00:00
|
|
|
import code.name.monkey.retromusic.repository.Repository
|
2020-08-13 17:08:37 +00:00
|
|
|
import code.name.monkey.retromusic.repository.SongRepository
|
|
|
|
import code.name.monkey.retromusic.service.MusicService
|
2021-09-08 18:30:20 +00:00
|
|
|
import kotlinx.coroutines.Dispatchers
|
2020-10-17 09:17:57 +00:00
|
|
|
import kotlinx.coroutines.GlobalScope
|
|
|
|
import kotlinx.coroutines.launch
|
2021-09-08 18:30:20 +00:00
|
|
|
import kotlinx.coroutines.withContext
|
2020-08-13 17:08:37 +00:00
|
|
|
import org.jaudiotagger.audio.AudioFileIO
|
|
|
|
import org.jaudiotagger.tag.FieldKey
|
2021-09-20 06:29:25 +00:00
|
|
|
import org.koin.core.component.KoinComponent
|
|
|
|
import org.koin.core.component.get
|
2020-08-13 17:08:37 +00:00
|
|
|
import java.io.File
|
|
|
|
import java.io.IOException
|
|
|
|
import java.util.*
|
|
|
|
import java.util.regex.Pattern
|
|
|
|
|
|
|
|
|
|
|
|
object MusicUtil : KoinComponent {
|
|
|
|
fun createShareSongFileIntent(song: Song, context: Context): Intent? {
|
|
|
|
return try {
|
|
|
|
Intent().setAction(Intent.ACTION_SEND).putExtra(
|
|
|
|
Intent.EXTRA_STREAM,
|
|
|
|
FileProvider.getUriForFile(
|
|
|
|
context,
|
|
|
|
context.applicationContext.packageName,
|
|
|
|
File(song.data)
|
|
|
|
)
|
|
|
|
).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).setType("audio/*")
|
|
|
|
} catch (e: IllegalArgumentException) {
|
|
|
|
// TODO the path is most likely not like /storage/emulated/0/... but something like /storage/28C7-75B0/...
|
|
|
|
e.printStackTrace()
|
|
|
|
Toast.makeText(
|
|
|
|
context,
|
|
|
|
"Could not share this file, I'm aware of the issue.",
|
|
|
|
Toast.LENGTH_SHORT
|
|
|
|
).show()
|
|
|
|
Intent()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun buildInfoString(string1: String?, string2: String?): String {
|
|
|
|
if (string1.isNullOrEmpty()) {
|
|
|
|
return if (string2.isNullOrEmpty()) "" else string2
|
|
|
|
}
|
|
|
|
return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1 • $string2"
|
|
|
|
}
|
|
|
|
|
2021-11-22 10:01:33 +00:00
|
|
|
fun createAlbumArtFile(context: Context): File {
|
2020-08-13 17:08:37 +00:00
|
|
|
return File(
|
2021-11-22 10:01:33 +00:00
|
|
|
createAlbumArtDir(context),
|
2020-08-13 17:08:37 +00:00
|
|
|
System.currentTimeMillis().toString()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-11-22 10:01:33 +00:00
|
|
|
private fun createAlbumArtDir(context: Context): File {
|
|
|
|
val albumArtDir = File(
|
|
|
|
if (VersionUtils.hasR()) context.cacheDir else Environment.getExternalStorageDirectory(),
|
|
|
|
"/albumthumbs/"
|
|
|
|
)
|
2020-08-13 17:08:37 +00:00
|
|
|
if (!albumArtDir.exists()) {
|
|
|
|
albumArtDir.mkdirs()
|
|
|
|
try {
|
|
|
|
File(albumArtDir, ".nomedia").createNewFile()
|
|
|
|
} catch (e: IOException) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return albumArtDir
|
|
|
|
}
|
|
|
|
|
2020-09-17 21:25:41 +00:00
|
|
|
fun deleteAlbumArt(context: Context, albumId: Long) {
|
2020-08-13 17:08:37 +00:00
|
|
|
val contentResolver = context.contentResolver
|
|
|
|
val localUri = Uri.parse("content://media/external/audio/albumart")
|
2020-11-16 17:37:16 +00:00
|
|
|
contentResolver.delete(ContentUris.withAppendedId(localUri, albumId), null, null)
|
2020-08-13 17:08:37 +00:00
|
|
|
contentResolver.notifyChange(localUri, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getArtistInfoString(
|
|
|
|
context: Context,
|
|
|
|
artist: Artist
|
|
|
|
): String {
|
|
|
|
val albumCount = artist.albumCount
|
|
|
|
val songCount = artist.songCount
|
|
|
|
val albumString =
|
|
|
|
if (albumCount == 1) context.resources.getString(R.string.album)
|
|
|
|
else context.resources.getString(R.string.albums)
|
|
|
|
val songString =
|
|
|
|
if (songCount == 1) context.resources.getString(R.string.song)
|
|
|
|
else context.resources.getString(R.string.songs)
|
|
|
|
return "$albumCount $albumString • $songCount $songString"
|
|
|
|
}
|
|
|
|
|
|
|
|
//iTunes uses for example 1002 for track 2 CD1 or 3011 for track 11 CD3.
|
|
|
|
//this method converts those values to normal tracknumbers
|
|
|
|
fun getFixedTrackNumber(trackNumberToFix: Int): Int {
|
|
|
|
return trackNumberToFix % 1000
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getLyrics(song: Song): String? {
|
2020-09-06 17:56:39 +00:00
|
|
|
var lyrics: String? = "No lyrics found"
|
2020-08-13 17:08:37 +00:00
|
|
|
val file = File(song.data)
|
|
|
|
try {
|
|
|
|
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
2021-09-08 18:30:20 +00:00
|
|
|
if (lyrics == null || lyrics.trim { it <= ' ' }.isEmpty() || AbsSynchronizedLyrics
|
2020-08-13 17:08:37 +00:00
|
|
|
.isSynchronized(lyrics)
|
|
|
|
) {
|
|
|
|
val dir = file.absoluteFile.parentFile
|
|
|
|
if (dir != null && dir.exists() && dir.isDirectory) {
|
|
|
|
val format = ".*%s.*\\.(lrc|txt)"
|
|
|
|
val filename = Pattern.quote(
|
|
|
|
FileUtil.stripExtension(file.name)
|
|
|
|
)
|
|
|
|
val songtitle = Pattern.quote(song.title)
|
|
|
|
val patterns =
|
|
|
|
ArrayList<Pattern>()
|
|
|
|
patterns.add(
|
|
|
|
Pattern.compile(
|
|
|
|
String.format(format, filename),
|
|
|
|
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE
|
|
|
|
)
|
|
|
|
)
|
|
|
|
patterns.add(
|
|
|
|
Pattern.compile(
|
|
|
|
String.format(format, songtitle),
|
|
|
|
Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE
|
|
|
|
)
|
|
|
|
)
|
|
|
|
val files =
|
|
|
|
dir.listFiles { f: File ->
|
|
|
|
for (pattern in patterns) {
|
|
|
|
if (pattern.matcher(f.name).matches()) {
|
|
|
|
return@listFiles true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
2020-09-06 17:56:39 +00:00
|
|
|
if (files != null && files.isNotEmpty()) {
|
2020-08-13 17:08:37 +00:00
|
|
|
for (f in files) {
|
|
|
|
try {
|
|
|
|
val newLyrics =
|
|
|
|
FileUtil.read(f)
|
2020-11-16 17:37:16 +00:00
|
|
|
if (newLyrics != null && newLyrics.trim { it <= ' ' }.isNotEmpty()) {
|
2020-08-13 17:08:37 +00:00
|
|
|
if (AbsSynchronizedLyrics.isSynchronized(newLyrics)) {
|
|
|
|
return newLyrics
|
|
|
|
}
|
|
|
|
lyrics = newLyrics
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return lyrics
|
|
|
|
}
|
|
|
|
|
2021-09-24 10:32:35 +00:00
|
|
|
@JvmStatic
|
2020-09-17 21:25:41 +00:00
|
|
|
fun getMediaStoreAlbumCoverUri(albumId: Long): Uri {
|
|
|
|
val sArtworkUri = Uri.parse("content://media/external/audio/albumart")
|
|
|
|
return ContentUris.withAppendedId(sArtworkUri, albumId)
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun getPlaylistInfoString(
|
|
|
|
context: Context,
|
|
|
|
songs: List<Song>
|
|
|
|
): String {
|
|
|
|
val duration = getTotalDuration(songs)
|
|
|
|
return buildInfoString(
|
|
|
|
getSongCountString(context, songs.size),
|
|
|
|
getReadableDurationString(duration)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-08-20 06:49:08 +00:00
|
|
|
fun playlistInfoString(
|
|
|
|
context: Context,
|
|
|
|
songs: List<SongEntity>
|
|
|
|
): String {
|
|
|
|
return getSongCountString(context, songs.size)
|
|
|
|
}
|
|
|
|
|
2021-09-08 18:30:20 +00:00
|
|
|
fun getReadableDurationString(songDurationMillis: Long): String {
|
2020-08-13 17:08:37 +00:00
|
|
|
var minutes = songDurationMillis / 1000 / 60
|
|
|
|
val seconds = songDurationMillis / 1000 % 60
|
|
|
|
return if (minutes < 60) {
|
|
|
|
String.format(
|
|
|
|
Locale.getDefault(),
|
|
|
|
"%02d:%02d",
|
|
|
|
minutes,
|
|
|
|
seconds
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
val hours = minutes / 60
|
2021-09-08 18:30:20 +00:00
|
|
|
minutes %= 60
|
2020-08-13 17:08:37 +00:00
|
|
|
String.format(
|
|
|
|
Locale.getDefault(),
|
|
|
|
"%02d:%02d:%02d",
|
|
|
|
hours,
|
|
|
|
minutes,
|
|
|
|
seconds
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-08 18:30:20 +00:00
|
|
|
fun getSectionName(mediaTitle: String?): String {
|
|
|
|
var musicMediaTitle = mediaTitle
|
2020-08-13 17:08:37 +00:00
|
|
|
return try {
|
|
|
|
if (TextUtils.isEmpty(musicMediaTitle)) {
|
|
|
|
return ""
|
|
|
|
}
|
2021-09-08 18:30:20 +00:00
|
|
|
musicMediaTitle = musicMediaTitle!!.trim { it <= ' ' }.lowercase()
|
2020-08-13 17:08:37 +00:00
|
|
|
if (musicMediaTitle.startsWith("the ")) {
|
|
|
|
musicMediaTitle = musicMediaTitle.substring(4)
|
|
|
|
} else if (musicMediaTitle.startsWith("a ")) {
|
|
|
|
musicMediaTitle = musicMediaTitle.substring(2)
|
|
|
|
}
|
|
|
|
if (musicMediaTitle.isEmpty()) {
|
|
|
|
""
|
2021-09-08 18:30:20 +00:00
|
|
|
} else musicMediaTitle.substring(0, 1).uppercase()
|
2020-08-13 17:08:37 +00:00
|
|
|
} catch (e: Exception) {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getSongCountString(context: Context, songCount: Int): String {
|
|
|
|
val songString = if (songCount == 1) context.resources
|
|
|
|
.getString(R.string.song) else context.resources.getString(R.string.songs)
|
|
|
|
return "$songCount $songString"
|
|
|
|
}
|
|
|
|
|
2020-09-17 21:25:41 +00:00
|
|
|
fun getSongFileUri(songId: Long): Uri {
|
2020-08-13 17:08:37 +00:00
|
|
|
return ContentUris.withAppendedId(
|
|
|
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
2021-09-08 18:30:20 +00:00
|
|
|
songId
|
2020-08-13 17:08:37 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-08 18:30:20 +00:00
|
|
|
fun getSongFilePath(context: Context, uri: Uri): String? {
|
|
|
|
val projection = arrayOf(MediaStore.MediaColumns.DATA)
|
|
|
|
return context.contentResolver.query(uri, projection, null, null, null)?.use {
|
|
|
|
if (it.moveToFirst()) {
|
|
|
|
it.getString(0)
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-13 17:08:37 +00:00
|
|
|
fun getTotalDuration(songs: List<Song>): Long {
|
|
|
|
var duration: Long = 0
|
|
|
|
for (i in songs.indices) {
|
|
|
|
duration += songs[i].duration
|
|
|
|
}
|
|
|
|
return duration
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getYearString(year: Int): String {
|
|
|
|
return if (year > 0) year.toString() else "-"
|
|
|
|
}
|
|
|
|
|
2020-09-17 21:25:41 +00:00
|
|
|
fun indexOfSongInList(songs: List<Song>, songId: Long): Int {
|
|
|
|
return songs.indexOfFirst { it.id == songId }
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun insertAlbumArt(
|
|
|
|
context: Context,
|
2020-09-17 21:25:41 +00:00
|
|
|
albumId: Long,
|
2020-08-13 17:08:37 +00:00
|
|
|
path: String?
|
|
|
|
) {
|
|
|
|
val contentResolver = context.contentResolver
|
2020-11-16 17:37:16 +00:00
|
|
|
val artworkUri = Uri.parse("content://media/external/audio/albumart")
|
|
|
|
contentResolver.delete(ContentUris.withAppendedId(artworkUri, albumId), null, null)
|
2020-08-13 17:08:37 +00:00
|
|
|
val values = ContentValues()
|
|
|
|
values.put("album_id", albumId)
|
|
|
|
values.put("_data", path)
|
|
|
|
contentResolver.insert(artworkUri, values)
|
|
|
|
contentResolver.notifyChange(artworkUri, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun isArtistNameUnknown(artistName: String?): Boolean {
|
|
|
|
if (TextUtils.isEmpty(artistName)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (artistName == Artist.UNKNOWN_ARTIST_DISPLAY_NAME) {
|
|
|
|
return true
|
|
|
|
}
|
2021-09-08 18:30:20 +00:00
|
|
|
val tempName = artistName!!.trim { it <= ' ' }.lowercase()
|
2020-08-13 17:08:37 +00:00
|
|
|
return tempName == "unknown" || tempName == "<unknown>"
|
|
|
|
}
|
|
|
|
|
2020-09-21 04:31:53 +00:00
|
|
|
fun isVariousArtists(artistName: String?): Boolean {
|
|
|
|
if (TextUtils.isEmpty(artistName)) {
|
|
|
|
return false
|
|
|
|
}
|
2020-09-21 04:17:53 +00:00
|
|
|
if (artistName == Artist.VARIOUS_ARTISTS_DISPLAY_NAME) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-08-13 17:08:37 +00:00
|
|
|
fun isFavorite(context: Context, song: Song): Boolean {
|
|
|
|
return PlaylistsUtil
|
2020-09-17 21:25:41 +00:00
|
|
|
.doPlaylistContains(context, getFavoritesPlaylist(context).id, song.id)
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun isFavoritePlaylist(
|
|
|
|
context: Context,
|
|
|
|
playlist: Playlist
|
|
|
|
): Boolean {
|
2020-08-21 18:01:52 +00:00
|
|
|
return playlist.name == context.getString(R.string.favorites)
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 09:17:57 +00:00
|
|
|
val repository = get<Repository>()
|
2020-08-13 17:08:37 +00:00
|
|
|
fun toggleFavorite(context: Context, song: Song) {
|
2020-10-17 09:17:57 +00:00
|
|
|
GlobalScope.launch {
|
2021-11-27 08:36:49 +00:00
|
|
|
val playlist: PlaylistEntity = repository.favoritePlaylist()
|
2020-10-17 09:17:57 +00:00
|
|
|
if (playlist != null) {
|
|
|
|
val songEntity = song.toSongEntity(playlist.playListId)
|
|
|
|
val isFavorite = repository.isFavoriteSong(songEntity).isNotEmpty()
|
|
|
|
if (isFavorite) {
|
|
|
|
repository.removeSongFromPlaylist(songEntity)
|
|
|
|
} else {
|
|
|
|
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
context.sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getFavoritesPlaylist(context: Context): Playlist {
|
|
|
|
return RealPlaylistRepository(context.contentResolver).playlist(context.getString(R.string.favorites))
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getOrCreateFavoritesPlaylist(context: Context): Playlist {
|
|
|
|
return RealPlaylistRepository(context.contentResolver).playlist(
|
|
|
|
PlaylistsUtil.createPlaylist(
|
|
|
|
context,
|
|
|
|
context.getString(R.string.favorites)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun deleteTracks(
|
|
|
|
activity: FragmentActivity,
|
|
|
|
songs: List<Song>,
|
|
|
|
safUris: List<Uri>?,
|
|
|
|
callback: Runnable?
|
|
|
|
) {
|
|
|
|
val songRepository: SongRepository = get()
|
|
|
|
val projection = arrayOf(
|
|
|
|
BaseColumns._ID, MediaStore.MediaColumns.DATA
|
|
|
|
)
|
|
|
|
// Split the query into multiple batches, and merge the resulting cursors
|
2020-08-21 18:01:52 +00:00
|
|
|
var batchStart: Int
|
2020-08-13 17:08:37 +00:00
|
|
|
var batchEnd = 0
|
|
|
|
val batchSize =
|
|
|
|
1000000 / 10 // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID
|
|
|
|
val songCount = songs.size
|
|
|
|
|
|
|
|
while (batchEnd < songCount) {
|
|
|
|
batchStart = batchEnd
|
|
|
|
|
|
|
|
val selection = StringBuilder()
|
|
|
|
selection.append(BaseColumns._ID + " IN (")
|
|
|
|
|
|
|
|
var i = 0
|
|
|
|
while (i < batchSize - 1 && batchEnd < songCount - 1) {
|
|
|
|
selection.append(songs[batchEnd].id)
|
|
|
|
selection.append(",")
|
|
|
|
i++
|
|
|
|
batchEnd++
|
|
|
|
}
|
|
|
|
// The last element of a batch
|
|
|
|
// The last element of a batch
|
|
|
|
selection.append(songs[batchEnd].id)
|
|
|
|
batchEnd++
|
|
|
|
selection.append(")")
|
|
|
|
|
|
|
|
try {
|
|
|
|
val cursor = activity.contentResolver.query(
|
|
|
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
|
|
|
|
null, null
|
|
|
|
)
|
|
|
|
if (cursor != null) {
|
|
|
|
// Step 1: Remove selected tracks from the current playlist, as well
|
|
|
|
// as from the album art cache
|
|
|
|
cursor.moveToFirst()
|
|
|
|
while (!cursor.isAfterLast) {
|
2020-09-17 21:25:41 +00:00
|
|
|
val id = cursor.getLong(BaseColumns._ID)
|
2020-08-13 17:08:37 +00:00
|
|
|
val song: Song = songRepository.song(id)
|
|
|
|
removeFromQueue(song)
|
|
|
|
cursor.moveToNext()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 2: Remove selected tracks from the database
|
|
|
|
activity.contentResolver.delete(
|
|
|
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
|
|
|
selection.toString(), null
|
|
|
|
)
|
|
|
|
// Step 3: Remove files from card
|
|
|
|
cursor.moveToFirst()
|
|
|
|
var index = batchStart
|
|
|
|
while (!cursor.isAfterLast) {
|
|
|
|
val name = cursor.getString(1)
|
|
|
|
val safUri =
|
|
|
|
if (safUris == null || safUris.size <= index) null else safUris[index]
|
|
|
|
SAFUtil.delete(activity, name, safUri)
|
|
|
|
index++
|
|
|
|
cursor.moveToNext()
|
|
|
|
}
|
|
|
|
cursor.close()
|
|
|
|
}
|
|
|
|
} catch (ignored: SecurityException) {
|
|
|
|
|
|
|
|
}
|
|
|
|
activity.contentResolver.notifyChange(Uri.parse("content://media"), null)
|
|
|
|
activity.runOnUiThread {
|
|
|
|
Toast.makeText(
|
|
|
|
activity,
|
|
|
|
activity.getString(R.string.deleted_x_songs, songCount),
|
|
|
|
Toast.LENGTH_SHORT
|
|
|
|
)
|
|
|
|
.show()
|
|
|
|
callback?.run()
|
|
|
|
}
|
2020-09-09 12:37:25 +00:00
|
|
|
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-09 12:37:25 +00:00
|
|
|
|
2021-09-08 18:30:20 +00:00
|
|
|
suspend fun deleteTracks(context: Context, songs: List<Song>) {
|
2020-11-16 17:37:16 +00:00
|
|
|
val projection = arrayOf(BaseColumns._ID, MediaStore.MediaColumns.DATA)
|
2020-09-09 12:37:25 +00:00
|
|
|
val selection = StringBuilder()
|
|
|
|
selection.append(BaseColumns._ID + " IN (")
|
|
|
|
for (i in songs.indices) {
|
|
|
|
selection.append(songs[i].id)
|
|
|
|
if (i < songs.size - 1) {
|
|
|
|
selection.append(",")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
selection.append(")")
|
|
|
|
var deletedCount = 0
|
|
|
|
try {
|
|
|
|
val cursor: Cursor? = context.contentResolver.query(
|
|
|
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
|
|
|
|
null, null
|
|
|
|
)
|
|
|
|
if (cursor != null) {
|
2021-11-15 05:56:59 +00:00
|
|
|
removeFromQueue(songs)
|
2020-09-09 12:37:25 +00:00
|
|
|
|
|
|
|
// Step 2: Remove files from card
|
|
|
|
cursor.moveToFirst()
|
|
|
|
while (!cursor.isAfterLast) {
|
|
|
|
val id: Int = cursor.getInt(0)
|
|
|
|
val name: String = cursor.getString(1)
|
|
|
|
try { // File.delete can throw a security exception
|
|
|
|
val f = File(name)
|
|
|
|
if (f.delete()) {
|
|
|
|
// Step 3: Remove selected track from the database
|
|
|
|
context.contentResolver.delete(
|
|
|
|
ContentUris.withAppendedId(
|
|
|
|
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
|
|
|
id.toLong()
|
|
|
|
), null, null
|
|
|
|
)
|
|
|
|
deletedCount++
|
|
|
|
} else {
|
|
|
|
// I'm not sure if we'd ever get here (deletion would
|
|
|
|
// have to fail, but no exception thrown)
|
|
|
|
Log.e("MusicUtils", "Failed to delete file $name")
|
|
|
|
}
|
|
|
|
cursor.moveToNext()
|
|
|
|
} catch (ex: SecurityException) {
|
|
|
|
cursor.moveToNext()
|
|
|
|
} catch (e: NullPointerException) {
|
|
|
|
Log.e("MusicUtils", "Failed to find file $name")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cursor.close()
|
|
|
|
}
|
2021-09-08 18:30:20 +00:00
|
|
|
withContext(Dispatchers.Main) {
|
|
|
|
Toast.makeText(
|
|
|
|
context,
|
|
|
|
context.getString(R.string.deleted_x_songs, deletedCount),
|
|
|
|
Toast.LENGTH_SHORT
|
|
|
|
).show()
|
|
|
|
}
|
|
|
|
|
2020-09-09 12:37:25 +00:00
|
|
|
} catch (ignored: SecurityException) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-22 10:01:33 +00:00
|
|
|
@RequiresApi(Build.VERSION_CODES.R)
|
2021-11-23 05:47:26 +00:00
|
|
|
fun deleteTracksR(activity: Activity, songs: List<Song>) {
|
2021-07-25 05:10:13 +00:00
|
|
|
val pendingIntent = MediaStore.createDeleteRequest(activity.contentResolver, songs.map {
|
|
|
|
getSongFileUri(it.id)
|
|
|
|
})
|
2021-11-27 08:36:49 +00:00
|
|
|
activity.startIntentSenderForResult(pendingIntent.intentSender, 45, null, 0, 0, 0, null)
|
2021-07-25 05:10:13 +00:00
|
|
|
}
|
|
|
|
|
2021-09-08 18:30:20 +00:00
|
|
|
fun songByGenre(genreId: Long): Song {
|
|
|
|
return repository.getSongByGenre(genreId)
|
|
|
|
}
|
2020-08-13 17:08:37 +00:00
|
|
|
}
|