diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2a4bbd8..0c9f587 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -45,4 +45,6 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
+ val fragment_version = "1.8.9"
+ implementation("androidx.fragment:fragment-ktx:$fragment_version")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 398b755..f3d93d4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,10 @@
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_25_2/MainActivity.kt b/app/src/main/java/com/example/android_25_2/MainActivity.kt
index aed359b..c4e6875 100644
--- a/app/src/main/java/com/example/android_25_2/MainActivity.kt
+++ b/app/src/main/java/com/example/android_25_2/MainActivity.kt
@@ -1,20 +1,184 @@
package com.example.android_25_2
+import android.Manifest
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
+import android.os.IBinder
+import android.provider.MediaStore
+import android.provider.Settings
+import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
class MainActivity : AppCompatActivity() {
+ private var isBound = false
+ private var musicService: MusicService? = null
+ private lateinit var player: ConstraintLayout
+ private lateinit var tvTitle: TextView
+ private lateinit var tvArtist: TextView
+
+ private val serviceConnection = object : ServiceConnection{
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?){
+ val binder = service as MusicService.MusicBinder
+ musicService = binder.getService()
+ isBound = true
+
+ musicService?.setOnMusicChangeListener { music ->
+ updatePlayer(music)
+ }
+ updatePlayer(musicService?.currentMusic)
+ }
+
+ override fun onServiceDisconnected(p0: ComponentName?) {
+ musicService = null
+ isBound = false
+ }
+ }
+ fun updatePlayer(music: MusicItem?) {
+ if (music != null) {
+ player.visibility = ConstraintLayout.VISIBLE
+ tvTitle.text = music.displayName
+ tvArtist.text = music.artist
+ } else {
+ player.visibility = ConstraintLayout.GONE
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
+
+ player = findViewById(R.id.player)
+ tvTitle = findViewById(R.id.player_title)
+ tvArtist = findViewById(R.id.player_artist)
+
+ permissionCheck()
+ createNotificationChannel()
+
+ val musicList = mutableListOf()
+
+ val projection = arrayOf(
+ MediaStore.Audio.Media.DISPLAY_NAME,
+ MediaStore.Audio.Media.DURATION,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media._ID
+ )
+
+ applicationContext.contentResolver.query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ projection,
+ "",
+ arrayOf(),
+ ""
+ )?.use { cursor ->
+ val displayNameColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
+ val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
+ val artistsColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
+ val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
+ while (cursor.moveToNext()){
+ val displayName = cursor.getString(displayNameColumn)
+ val duration = cursor.getInt(durationColumn)
+ val artists = cursor.getString(artistsColumn)
+ val id = cursor.getString(idColumn)
+ val uri = Uri.withAppendedPath(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ id.toString()
+ )
+ musicList.add(MusicItem(displayName, duration, artists, uri))
+ }
+ }
+
+ val recyclerView = findViewById(R.id.recyclerView)
+ val musicAdapter = MusicAdapter(musicList) { music ->
+ playMusicWithService(music)
+ }
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ recyclerView.adapter = musicAdapter
+ }
+
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
+ val channel = NotificationChannel(
+ "music_channel",
+ "Music Playback",
+ NotificationManager.IMPORTANCE_LOW
+ )
+ val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ private fun playMusicWithService(music: MusicItem) {
+ val intent = Intent(this, MusicService::class.java).apply {
+ action = "ACTION_PLAY"
+ putExtra("MUSIC_TITLE", music.displayName)
+ putExtra("MUSIC_ARTIST", music.artist)
+ putExtra("MUSIC_URI", music.uri.toString())
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(intent)
+ } else {
+ startService(intent)
+ }
+ bindService(Intent(this, MusicService::class.java), serviceConnection, Context.BIND_AUTO_CREATE)
+ }
+
+ private fun permissionCheck() {
+ val mediaPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_AUDIO
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ }
+ if (ContextCompat.checkSelfPermission(this, mediaPermission)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ this, arrayOf(mediaPermission), 1000
+ )
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED){
+ ActivityCompat.requestPermissions(
+ this, arrayOf(Manifest.permission.POST_NOTIFICATIONS
+ ), 1000
+ )
+ }
+ }
+
+ if(!ActivityCompat.shouldShowRequestPermissionRationale(this, mediaPermission)) {
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(Uri.parse("package:${packageName}"))
+ startActivity(intent)
+ }
+ if(!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
+ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(Uri.parse("package:${packageName}"))
+ startActivity(intent)
+ }
+ }
+ }
+ override fun onDestroy() {
+ super.onDestroy()
+ if (isBound) {
+ unbindService(serviceConnection)
+ isBound = false
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_25_2/MusicAdapter.kt b/app/src/main/java/com/example/android_25_2/MusicAdapter.kt
new file mode 100644
index 0000000..c1c8bdc
--- /dev/null
+++ b/app/src/main/java/com/example/android_25_2/MusicAdapter.kt
@@ -0,0 +1,49 @@
+package com.example.android_25_2
+
+import android.content.Context
+import android.media.MediaPlayer
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.constraintlayout.motion.widget.MotionScene.Transition.TransitionOnClick
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
+
+class MusicAdapter(private val musicList: List, private val onClick: (MusicItem) -> Unit): RecyclerView.Adapter(){
+ private var mediaPlayer: MediaPlayer? = null
+
+ class MusicViewHolder(view: View) : RecyclerView.ViewHolder(view){
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(R.layout.item, parent, false)
+
+ return MusicViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val music = musicList[position]
+ val displayName: TextView = holder.itemView.findViewById(R.id.text_display_name)
+ val artists: TextView = holder.itemView.findViewById(R.id.text_artist)
+ val duration: TextView = holder.itemView.findViewById(R.id.text_duration)
+ displayName.text = music.displayName
+ artists.text = music.artist
+ duration.text = music.duration.toString()
+
+ holder.itemView.setOnClickListener{
+ mediaPlayer?.stop()
+ mediaPlayer?.release()
+ onClick(music)
+
+ mediaPlayer = MediaPlayer().apply {
+ setDataSource(holder.itemView.context, music.uri)
+ prepare()
+ start()
+ }
+ }
+ }
+
+ override fun getItemCount() = musicList.size
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_25_2/MusicItem.kt b/app/src/main/java/com/example/android_25_2/MusicItem.kt
new file mode 100644
index 0000000..a129658
--- /dev/null
+++ b/app/src/main/java/com/example/android_25_2/MusicItem.kt
@@ -0,0 +1,10 @@
+package com.example.android_25_2
+
+import android.net.Uri
+
+data class MusicItem (
+ val displayName: String,
+ val duration: Int,
+ val artist: String,
+ val uri: Uri
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/android_25_2/MusicService.kt b/app/src/main/java/com/example/android_25_2/MusicService.kt
new file mode 100644
index 0000000..3313ecb
--- /dev/null
+++ b/app/src/main/java/com/example/android_25_2/MusicService.kt
@@ -0,0 +1,114 @@
+package com.example.android_25_2
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Intent
+import android.media.MediaPlayer
+import android.net.Uri
+import android.os.Binder
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+
+class MusicService: Service(){
+ private var mediaPlayer: MediaPlayer? = null
+ var currentMusic: MusicItem? = null
+ private val binder = MusicBinder()
+ private var musicChangeListener: ((MusicItem?) -> Unit)? = null
+
+ inner class MusicBinder : Binder() {
+ fun getService(): MusicService = this@MusicService
+ }
+
+ fun setOnMusicChangeListener(listener: (MusicItem?) -> Unit) {
+ musicChangeListener = listener
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startID: Int): Int{
+ when (intent?.action) {
+ "ACTION_PLAY" -> {
+ val title = intent.getStringExtra("MUSIC_TITLE")?: ""
+ val artist = intent.getStringExtra("MUSIC_ARTIST")?: ""
+ val uriString = intent.getStringExtra("MUSIC_URI")?: ""
+ val uri = Uri.parse(uriString)
+
+ val music = MusicItem(title, 0, artist, uri)
+ playMusic(music)
+ }
+ "ACTION_STOP" -> {
+ stopMusic()
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ stopSelf()
+ }
+ }
+ return START_STICKY
+ }
+
+ private fun playMusic(music: MusicItem) {
+ try {
+ mediaPlayer?.stop()
+ mediaPlayer?.release()
+
+ mediaPlayer = MediaPlayer().apply {
+ setDataSource(applicationContext, music.uri)
+ prepare()
+ start()
+ }
+
+ currentMusic = music
+ musicChangeListener?.invoke(music)
+ startForeground(1, createNotification(music))
+
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ private fun stopMusic() {
+ mediaPlayer?.stop()
+ mediaPlayer?.release()
+ mediaPlayer = null
+ currentMusic = null
+ musicChangeListener?.invoke(null)
+ }
+
+ private fun createNotification(music: MusicItem): Notification {
+ val intent = Intent(this, MainActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ }
+ val pendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ intent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val stopIntent = Intent(this, MusicService::class.java).apply {
+ action = "ACTION_STOP"
+ }
+ val stopPendingIntent = PendingIntent.getService(
+ this,
+ 1,
+ stopIntent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+
+ return NotificationCompat.Builder(this, "music_channel")
+ .setContentTitle(music.displayName)
+ .setContentText(music.artist)
+ .setSmallIcon(android.R.drawable.ic_media_play)
+ .setContentIntent(pendingIntent)
+ .addAction(android.R.drawable.ic_media_pause, "Stop", stopPendingIntent)
+ .setOngoing(true)
+ .build()
+ }
+
+ override fun onBind(intent: Intent?): IBinder {
+ return binder
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ stopMusic()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 9affce0..254902b 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -7,4 +7,49 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item.xml b/app/src/main/res/layout/item.xml
new file mode 100644
index 0000000..4e691c8
--- /dev/null
+++ b/app/src/main/res/layout/item.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index a92bfca..3167c98 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -2,12 +2,12 @@