Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 17 additions & 18 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:allowBackup="true"
Expand All @@ -24,6 +25,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".PlayMusic"
android:permission="android.permission.BIND_JOB_SERVICE"
android:foregroundServiceType="mediaPlayback" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ class AuthorityFragment : Fragment() {
}

private fun requestPermission() {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
} else if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.FOREGROUND_SERVICE) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(Manifest.permission.FOREGROUND_SERVICE)
} else {
// 권한이 이미 있음
openFragment()
} else {
// 권한이 없으면 권한 요청
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}

Expand Down
60 changes: 46 additions & 14 deletions app/src/main/java/com/example/assignment02/ListFragment.kt
Original file line number Diff line number Diff line change
@@ -1,57 +1,83 @@
package com.example.assignment02

import android.Manifest
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.BatteryManager
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.io.File

class ListFragment : Fragment() {

private val data = mutableListOf<Item>()
private lateinit var adapter: Adapter
private lateinit var name: TextView
private lateinit var time: TextView
private lateinit var energy: LinearLayout

companion object {
fun newInstance(): ListFragment {
return ListFragment()
}
}

@SuppressLint("MissingInflatedId")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_list, container, false)

name = view.findViewById(R.id.name)
time = view.findViewById(R.id.time)
energy = view.findViewById(R.id.energe)

// 배터리에 따라 배경색 변경하기!
val battery = getBatteryData(requireContext()) // 0~100(%)
energy.backgroundTintList = ColorStateList.valueOf(ColorUtils.blendARGB(Color.parseColor("#00B0FF"), Color.parseColor("#FFFFFF"), battery / 100f))

val recycler_list: RecyclerView = view.findViewById(R.id.recycler_list)
recycler_list.layoutManager = LinearLayoutManager(requireContext())
adapter = Adapter(data)
adapter = Adapter(data, name, time)
recycler_list.adapter = adapter

if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
) {
// 권한이 있음 -> 음악 파일 추가
getMusicFilesData()
} else {
// 권한이 없음
openFragment()
}
else if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.FOREGROUND_SERVICE) != PackageManager.PERMISSION_GRANTED
) {
openFragment()
}
else {
// 권한이 있음
getMusicFilesData()
}

return view
}

fun getBatteryData(context: Context): Int {
val batteryManager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}

fun getMusicFilesData() {
val contentResolver: ContentResolver = requireContext().contentResolver

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리사이클러뷰는 snake_case로 선언하시고 여기서는 camelCase를 사용하셨는데, 이렇게 통일하지 않고 사용하시면 코드의 가독성이 떨어질 수 있어요. 웬만해서는 네이밍 컨벤션 통일해주세요!

val uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
Expand All @@ -71,9 +97,7 @@ class ListFragment : Fragment() {
val dataIndex = it.getColumnIndex(MediaStore.Audio.Media.DATA)

while (it.moveToNext()) {
Log.d("File Path", "음악 파일 경로: ${it.getString(dataIndex)}")

val item = Item(it.getString(titleIndex) ?: "Unknown Title", it.getString(artistIndex) ?: "Unknown Artist", it.getLong(durationIndex) ?: 0)
val item = Item(it.getString(titleIndex) ?: "Unknown Title", it.getString(artistIndex) ?: "Unknown Artist", it.getLong(durationIndex) ?: 0, it.getString(dataIndex) ?: "")
data.add(item)
}
adapter.notifyDataSetChanged()
Expand All @@ -91,7 +115,7 @@ class ListFragment : Fragment() {
.commit()
}

class Adapter(private val data: MutableList<Item>) : RecyclerView.Adapter<Adapter.ViewHolder>() {
class Adapter(private val data: MutableList<Item>, private var name_view:TextView, private var time_view:TextView) : RecyclerView.Adapter<Adapter.ViewHolder>() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recyclerview의 아이템 변동 사항이 없다면 MutableList 보단 불변 List를 사용하는 게 좋습니다!
불변성을 보장해 데이터 무결성을 유지할 수 있어용


class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.findViewById(R.id.title)
Expand All @@ -111,11 +135,19 @@ class ListFragment : Fragment() {
val m = (item.duration/60000).toString()
val s = (item.duration/1000 %60).toString()
holder.duration.text = "$m:$s"

holder.itemView.setOnClickListener {
name_view.text = item.title
time_view.text = holder.duration.text
val intent = Intent(holder.itemView.context, PlayMusic::class.java).apply {
putExtra("uri", item.uri)
}
ContextCompat.startForegroundService(holder.itemView.context, intent)
}
}

override fun getItemCount(): Int = data.size
}

data class Item(val title: String, val artist: String, val duration: Long)

data class Item(val title: String, val artist: String, val duration: Long, val uri: String)
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/example/assignment02/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ class MainActivity : AppCompatActivity() {
.replace(R.id.fragment_container, fragment)
.commit()
}
override fun onBackPressed() {
super.onBackPressed()
finish()
}
}
77 changes: 77 additions & 0 deletions app/src/main/java/com/example/assignment02/PlayMusic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.example.assignment02

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import android.net.Uri
import android.util.Log

class PlayMusic : Service() {

private var mediaPlayer: MediaPlayer? = null
private val channel1 = "channel1"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

companion object로 선언해주는 게 좋아보입니당


override fun onCreate() {
super.onCreate()

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
channel1,
"music",
NotificationManager.IMPORTANCE_LOW
)
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}

val intent = Intent(this, MainActivity::class.java)
val pending_intent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)

// 알림 생성
val notification: Notification = NotificationCompat.Builder(this, channel1)
.setContentTitle("음악 재생 중...")
.setContentText("백그라운드에서 음악이 재생되고 있습니다.")
.setSmallIcon(androidx.core.R.drawable.ic_call_answer)
.setContentIntent(pending_intent)
.setAutoCancel(true)
.build()

startForeground(1, notification)
}

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val uri_string = intent.getStringExtra("uri")
Log.d("PlayMusic", "URI: $uri_string")
uri_string?.let {
val uri = Uri.parse(it)
musicPlay(uri)
}
return START_STICKY
}

private fun musicPlay(uri: Uri) {
mediaPlayer = MediaPlayer().apply {
setDataSource(applicationContext, uri)
setOnPreparedListener {
it.start()
}
prepareAsync()
}
}

override fun onBind(intent: Intent?): IBinder? {
return null
}

override fun onDestroy() {
super.onDestroy()
mediaPlayer?.release()
}
}
41 changes: 40 additions & 1 deletion app/src/main/res/layout/fragment_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="wrap_content"
android:layout_weight="1" >

</androidx.recyclerview.widget.RecyclerView>

<LinearLayout
android:id="@+id/energe"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#606060"
android:backgroundTint="#00B0FF"
android:orientation="vertical"
android:padding="20dp">

<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="재생중인 음악" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="없음" />

<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00" />
</LinearLayout>
</LinearLayout>

</LinearLayout>