diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c80941..dec17f8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,12 @@ + + + + + + () + + + private val musicPermission: String + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Manifest.permission.READ_MEDIA_AUDIO + } else { + Manifest.permission.READ_EXTERNAL_STORAGE + } + + private val requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + loadMusicFiles() + Toast.makeText(this, "allow", Toast.LENGTH_SHORT).show() + } else { + showPermissionDeniedDialog() + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + initViews() + checkAndRequestPermission() + } + + private fun initViews() { + recyclerView = findViewById(R.id.recycler_view_music) + musicAdapter = MusicAdapter(musicList) + recyclerView.apply { + layoutManager = LinearLayoutManager(this@MainActivity) + adapter = musicAdapter + } + } + + private fun checkAndRequestPermission() { + if (ContextCompat.checkSelfPermission(this, musicPermission) == PackageManager.PERMISSION_GRANTED) { + loadMusicFiles() + } else { + showPermissionRequestDialog() + } + } + + private fun showPermissionRequestDialog() { + AlertDialog.Builder(this) + .setTitle("음악 및 오디오 접근 권한") + .setMessage("음악 파일을 불러오기 위해 권한이 필요합니다.") + .setPositiveButton("Allow") { _, _ -> + requestPermissionLauncher.launch(musicPermission) + } + .setNegativeButton("Don't allow") { _, _ -> + showPermissionDeniedDialog() + } + .setCancelable(false) + .show() + } + + private fun showPermissionDeniedDialog() { + AlertDialog.Builder(this) + .setTitle("권한 필요!") + .setMessage("음악 파일을 표시하려면 권한이 필요합니다.\n\nAllow permission to show files") + .setPositiveButton("Open Settings") { _, _ -> + openAppSettings() + } + .setNegativeButton("Cancel") { _, _ -> + finish() + } + .setCancelable(false) + .show() + } + + private fun openAppSettings() { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + data = Uri.fromParts("package", packageName, null) + } + startActivity(intent) + } + + private fun loadMusicFiles() { + + val file = File("/sdcard/Music/Lil_Tecca_Dark_Thoughts.mp3") + if (file.exists()) { + MediaScannerConnection.scanFile(this, arrayOf(file.absolutePath), null, null) + } + + if (file.exists()) { + val uri = Uri.fromFile(file) + sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) + } + + musicList.clear() + + val projection = arrayOf( + MediaStore.Audio.Media._ID, + MediaStore.Audio.Media.TITLE, + MediaStore.Audio.Media.ARTIST, + MediaStore.Audio.Media.DURATION, + MediaStore.Audio.Media.ALBUM_ID, + MediaStore.Audio.Media.DATA + ) + + val selection = "${MediaStore.Audio.Media.IS_MUSIC} = 1" + val sortOrder = "${MediaStore.Audio.Media.TITLE} ASC" + + val cursor: Cursor? = contentResolver.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + projection, + selection, + null, + sortOrder + ) + + cursor?.use { + val idColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media._ID) + val titleColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE) + val artistColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST) + val durationColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION) + val albumIdColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID) + val dataColumn = it.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA) + + while (it.moveToNext()) { + val id = it.getLong(idColumn) + val title = it.getString(titleColumn) ?: "Unknown Title" + val artist = it.getString(artistColumn) ?: "Unknown Artist" + val duration = formatDuration(it.getLong(durationColumn)) + val albumId = it.getLong(albumIdColumn) + val data = it.getString(dataColumn) ?: "" + + musicList.add(MusicItem(id, title, artist, duration, albumId, data)) + } + } + + musicAdapter.notifyDataSetChanged() + } + + private fun formatDuration(durationMs: Long): String { + val minutes = (durationMs / 1000) / 60 + val seconds = (durationMs / 1000) % 60 + return String.format("%02d:%02d", minutes, seconds) + } + + override fun onResume() { + super.onResume() + if (ContextCompat.checkSelfPermission(this, musicPermission) == PackageManager.PERMISSION_GRANTED && musicList.isEmpty()) { + loadMusicFiles() + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt b/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt new file mode 100644 index 0000000..f65ad9a --- /dev/null +++ b/app/src/main/java/com/example/bcsd_android_2025_1/MusicAdapter.kt @@ -0,0 +1,55 @@ +package com.example.bcsd_android_2025_1 + +import android.content.ContentUris +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions + +class MusicAdapter( + private val musicList: List +) : RecyclerView.Adapter() { + + inner class MusicViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val albumArt: ImageView = itemView.findViewById(R.id.iv_album_art) + val title: TextView = itemView.findViewById(R.id.tv_title) + val artist: TextView = itemView.findViewById(R.id.tv_artist) + val duration: TextView = itemView.findViewById(R.id.tv_duration) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.activity_music, parent, false) + return MusicViewHolder(view) + } + + override fun onBindViewHolder(holder: MusicViewHolder, position: Int) { + val musicItem = musicList[position] + + holder.title.text = musicItem.title + holder.artist.text = musicItem.artist + holder.duration.text = musicItem.duration + + val albumArtUri = ContentUris.withAppendedId( + Uri.parse("content://media/external/audio/albumart"), + musicItem.albumId + ) + + Glide.with(holder.itemView.context) + .load(albumArtUri) + .apply( + RequestOptions() + .placeholder(R.drawable.ic_music_note) + .error(R.drawable.ic_music_note) + .centerCrop() + ) + .into(holder.albumArt) + } + + override fun getItemCount(): Int = musicList.size +} diff --git a/app/src/main/res/drawable/album_art_background.xml b/app/src/main/res/drawable/album_art_background.xml new file mode 100644 index 0000000..b41af79 --- /dev/null +++ b/app/src/main/res/drawable/album_art_background.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_music_note.xml b/app/src/main/res/drawable/ic_music_note.xml new file mode 100644 index 0000000..b41af79 --- /dev/null +++ b/app/src/main/res/drawable/ic_music_note.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_music.xml b/app/src/main/res/layout/activity_music.xml new file mode 100644 index 0000000..8a43ee9 --- /dev/null +++ b/app/src/main/res/layout/activity_music.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_permission.xml b/app/src/main/res/layout/activity_permission.xml new file mode 100644 index 0000000..f187a69 --- /dev/null +++ b/app/src/main/res/layout/activity_permission.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c8524cd..4c3cc91 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,4 +2,15 @@ #FF000000 #FFFFFFFF + #4CAF50 + #03DAC5 + #018786 + #6200EE + #3700B3 + #FF9800 + #757575 + #212121 + #F5F5F5 + #FFFFFF + #6200EE \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6c4daf..9a9db08 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,26 @@ - + BCSD_Android_2025-1 + First Fragment + Second Fragment + Next + Previous + more + back + Alarm + choice + random + text + Result: %1$d + Music Player + 음악 및 오디오 접근 권한 + Allow BCSD_Android_2025-1 to access music and audio on this device? + Allow + Don\'t allow + Permission Required! + Press OK to allow permission! + 권한 필요! + 음악 파일을 표시하려면 권한이 필요합니다.\n\nAllow permission to show files + Open Settings + Cancel + OK \ No newline at end of file