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
File renamed without changes.
3 changes: 3 additions & 0 deletions Assignment_9/.idea/.gitignore

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

File renamed without changes.
File renamed without changes.

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

File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion newProject/.idea/misc.xml → Assignment_9/.idea/misc.xml

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

File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}

dependencies {
Expand All @@ -42,6 +45,7 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.fragment.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
File renamed without changes.
43 changes: 43 additions & 0 deletions Assignment_9/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Assignment">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver android:name=".BatteryReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BATTERY_CHANGED"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
<action android:name="android.intent.action.BATTERY_LOW"/>
</intent-filter>
</receiver>

<service android:name=".MusicService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.assignment

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.BatteryManager
import android.widget.Toast

class BatteryReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BATTERY_CHANGED) {
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPct = level * 100 / scale.toFloat()

when (batteryPct) {
15f -> Toast.makeText(context, "배터리가 15% 이하입니다. 충전이 필요합니다.", Toast.LENGTH_SHORT).show()
50f -> Toast.makeText(context, "배터리가 50% 남았습니다.", Toast.LENGTH_SHORT).show()
90f -> Toast.makeText(context, "배터리가 90% 남았습니다.", Toast.LENGTH_SHORT).show()
}
}
}
}
151 changes: 151 additions & 0 deletions Assignment_9/app/src/main/java/com/example/assignment/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.example.assignment

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.assignment.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var musicListAdapter: MusicListAdapter
private lateinit var musicListRecyclerView: RecyclerView
private var isPlaying = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
requestPermissions()

if (hasPermissions(permissionList)) {
viewMusicList()
}

binding.playPauseButton.setOnClickListener { togglePlayPause() }
}

private val permissionList = arrayOf(
Manifest.permission.READ_MEDIA_AUDIO,
Manifest.permission.POST_NOTIFICATIONS,
Manifest.permission.FOREGROUND_SERVICE,
Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK
)

private val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
val allGranted = permissions.filter { !it.value }.keys

if (allGranted.isEmpty()) {
Toast.makeText(this, "모든 권한이 허용되었습니다.", Toast.LENGTH_SHORT).show()
}
else {
val deniedMessage = "권한이 거부되었습니다: ${allGranted.joinToString(", ")}"
Toast.makeText(this, deniedMessage, Toast.LENGTH_LONG).show()
}
}

@SuppressLint("ObsoleteSdkInt")
private fun hasPermission(permission: String): Boolean {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(
this,
permission
) == PackageManager.PERMISSION_GRANTED
}

@SuppressLint("ObsoleteSdkInt")
private fun hasPermissions(permissions: Array<String>): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true
}
return permissions.all { hasPermission(it) }
}

private fun requestPermissions() {
if (!hasPermissions(permissionList)) {
permissionLauncher.launch(permissionList)
}
}

private fun togglePlayPause() {
if (isPlaying) {
sendBroadcast(Intent(MusicService.ACTION_PAUSE))
binding.playPauseButton.setImageResource(R.drawable.ic_play)
}
else {
sendBroadcast((Intent(MusicService.ACTION_REPLAY)))
binding.playPauseButton.setImageResource(R.drawable.ic_pause)
}
isPlaying = !isPlaying
}

private fun viewMusicList() {
musicListRecyclerView = binding.musicListRecyclerView
musicListRecyclerView.layoutManager = LinearLayoutManager(this)

val musicList = loadMusic()
musicListAdapter = MusicListAdapter(musicList) { musicItem ->
playMusic(musicItem)
}
musicListRecyclerView.adapter = musicListAdapter
}

private fun loadMusic(): List<MusicItem> {
val musicList = mutableListOf<MusicItem>()
val projection = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.DURATION
)

val cursor = contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
MediaStore.Audio.Media.TITLE
)

cursor?.use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
val title = it.getString(it.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE))
val artist = it.getString(it.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST))
val duration = it.getString(it.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION))

musicList.add(MusicItem(id, title, artist, duration))
}
}

if (musicList.isEmpty()) {
Log.e("MainActivity", "로드할 음악 파일 없음")
} else {
Log.d("MainActivity", "${musicList.size}개의 파일 로드 완료")
}
return musicList
}

private fun playMusic(musicItem: MusicItem) {
val intent = Intent(this, MusicService::class.java).apply {
putExtra("MusicUri", Uri.withAppendedPath(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, musicItem.id.toString()))
putExtra("MusicName", musicItem.title)
}
startService(intent)
binding.musicTitleTextButton.text = musicItem.title

binding.playPauseButton.setImageResource(R.drawable.ic_pause)
isPlaying = true
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.assignment

data class MusicItem(
val id: Long,
val title: String,
val artist: String,
val duration: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.assignment

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.assignment.databinding.ItemMusicListBinding

class MusicListAdapter(
private val musicList: List<MusicItem>,
private val onItemClick: (MusicItem) -> Unit
) : RecyclerView.Adapter<MusicListAdapter.MusicViewHolder>() {

class MusicViewHolder(private val binding: ItemMusicListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(musicItem: MusicItem, onItemClick: (MusicItem) -> Unit) {
binding.titleTextView.text = musicItem.title
binding.artistTextView.text = musicItem.artist
binding.durationTextView.text = formatDuration(musicItem.duration.toLong())

itemView.setOnClickListener {
onItemClick(musicItem)
}
}

@SuppressLint("DefaultLocale")
private fun formatDuration(durationMs: Long): String {
val seconds = durationMs / 1000 % 60
val minutes = durationMs / 1000 / 60 % 60
return String.format("%02d:%02d", minutes, seconds)
}

}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MusicViewHolder {
val binding = ItemMusicListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MusicViewHolder(binding)
}

override fun onBindViewHolder(holder: MusicViewHolder, position: Int) {
val musicItem = musicList[position]
holder.bind(musicItem, onItemClick)
}

override fun getItemCount() = musicList.size

}
Loading