diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ce2a603..28820c8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,6 +16,9 @@
+
+
+
(R.id.seekBar)
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
- // Check if the seek bar is at 90%
- if (progress >= 90 ) {
+ if (progress >= 90) {
stopAlarm(seekBar)
}
}
- override fun onStartTrackingTouch(seekBar: SeekBar?) {}
+ override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
- refreshCycle()
- if (alarm != null) {
- alarmRing(alarm.ringtoneUri)
- }
- //TODO Release wake lock
- val alarmHandler = AlarmHandler()
- alarmHandler.setNextAlarm(applicationContext)
}
- private fun refreshCycle() {
- val progressBar = findViewById(R.id.progress_cycle)
- val progressText = findViewById(R.id.progress_text)
- val handler = Handler()
- val delayMillis = 1000
- val maxProgress = 100
-
- val updateRunnable = object : Runnable {
- var i = 0
-
- override fun run() {
- val currentTime = System.currentTimeMillis()
- val sdf = SimpleDateFormat("HH:mm")
- val formattedTime = sdf.format(Date(currentTime))
- val percentage = getPercentageOfDay().toLong()
- if (percentage <= maxProgress) {
- progressText.text = formattedTime
- progressBar.progress = percentage.toInt()
- handler.postDelayed(this, delayMillis.toLong())
- } else {
- // Reset progress bar and text
- progressBar.progress = 0
- progressText.text = "00:00"
- // Start the loop again
- handler.postDelayed(this, delayMillis.toLong())
- }
- }
- }
-
- handler.post(updateRunnable)
+ private fun alarmRing(ringtoneUri: String) {
+ val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ val audioAttributes = AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .build()
+
+ val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+ .setAudioAttributes(audioAttributes)
+ .setOnAudioFocusChangeListener { /* Handle focus change */ }
+ .build()
+
+ if (audioManager.requestAudioFocus(focusRequest) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ wakePhone()
+ playRingtone(ringtoneUri)
+ }
}
- private fun alarmRing(ringtoneUri: String) {
- // Wake Phone
- val powerManager: PowerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
+ private fun wakePhone() {
+ val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
wakeLock = powerManager.newWakeLock(
- PowerManager.ACQUIRE_CAUSES_WAKEUP or
- PowerManager.ON_AFTER_RELEASE or
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "rooster:wakelock"
- )
- wakeLock?.acquire()
-
- vibrator = applicationContext.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- // it is safe to cancel other vibrations currently taking place
- vibrator!!.cancel()
- val soundUri = if (ringtoneUri == "default") {
- // Fallback to a default sound if the URI is null or empty
- Uri.parse("android.resource://${applicationContext.packageName}/raw/alarmclock")
- } else {
- // Use the provided URI
- Uri.parse(ringtoneUri)
- }
+ PowerManager.PARTIAL_WAKE_LOCK or
+ PowerManager.ACQUIRE_CAUSES_WAKEUP or
+ PowerManager.ON_AFTER_RELEASE, "rooster:wakelock"
+ ).apply { acquire(10 * 60 * 1000L /*10 minutes*/) }
+ }
+
+ private fun playRingtone(ringtoneUri: String) {
+ vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ vibrator?.let {
+ it.cancel()
+ val uri = if (ringtoneUri == "default") Uri.parse("android.resource://${packageName}/raw/alarmclock") else Uri.parse(ringtoneUri)
+
mediaPlayer = MediaPlayer().apply {
- setDataSource(applicationContext, soundUri)
setAudioAttributes(
AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .setUsage(AudioAttributes.USAGE_ALARM)
.build()
)
- setOnCompletionListener {
- // Release MediaPlayer resources after completion
- it?.release()
- }
- prepareAsync()
- setOnPreparedListener { player ->
- // Start playback when prepared
- player.start()
- startAlarmLoop()
+ try {
+ setDataSource(applicationContext, uri)
+ prepare() // Prepare the MediaPlayer asynchronously
+ setOnErrorListener { _, what, extra ->
+ Log.e("MediaPlayer Error", "What: $what, Extra: $extra")
+ true
+ }
+ setOnPreparedListener { start() }
+ isLooping = true
+ } catch (e: Exception) {
+ Log.e("MediaPlayer", "Error setting data source", e)
}
- isLooping = true
}
}
}
- private fun startAlarmLoop() {
- // Start a loop that checks a SharedPreferences for a flag to stop the vibration
- val vibesPattern = createWaveformVibrationPattern(longArrayOf(0, 1000, 2000, 3000), repeat = -1)
- var vibrationEffect1 = VibrationEffect.createWaveform(vibesPattern, 3)
- CoroutineScope(Dispatchers.Default).launch {
- isVibrating = true
- vibrator?.vibrate(vibrationEffect1)
- if (alarmIsRunning) {
- mediaPlayer?.start()
- }
- while (isVibrating) {
- Log.w("Rooster Alarm", "Ring")
- // Check for alarmIsRunning flag to control MediaPlayer and vibration
- if (alarmIsRunning) {
- delay(500)
- } else {
- isVibrating = false
- break
- }
- delay(1000)
+
+ // Remaining methods including refreshCycle, getPercentageOfDay, stopAlarm, onResume, onPause, releaseResources...
+
+ private fun releaseResources() {
+ mediaPlayer?.let {
+ it.stop()
+ it.release()
+ }
+ mediaPlayer = null
+ wakeLock?.let {
+ if (it.isHeld) {
+ it.release()
}
}
+ wakeLock = null
+ vibrator?.cancel()
}
- fun createWaveformVibrationPattern(timings: LongArray, repeat: Int = -1): LongArray {
- var pattern = LongArray(timings.size + 1)
- pattern[0] = 0L
- for (i in 1 until pattern.size) {
- pattern[i] = pattern[i - 1] + timings[i - 1]
- }
+ fun stopAlarm(view: View?) {
+ alarmIsRunning = false
+ isVibrating = false
+ releaseResources()
+ finish()
+ }
- if (repeat != -1) {
- val repeatIndex = pattern[repeat]
- val repeatPattern = pattern.copyOfRange(repeatIndex.toInt(), pattern.size)
- pattern = pattern.copyOfRange(0, repeatIndex.toInt()) + repeatPattern
+ private fun refreshCycle() {
+ val progressBar = findViewById(R.id.progress_cycle)
+ val progressText = findViewById(R.id.progress_text)
+ val handler = Handler()
+ val delayMillis = 1000
+ val maxProgress = 100
+
+ val updateRunnable = object : Runnable {
+ var i = 0
+
+ override fun run() {
+ val currentTime = System.currentTimeMillis()
+ val sdf = SimpleDateFormat("HH:mm")
+ val formattedTime = sdf.format(Date(currentTime))
+ val percentage = getPercentageOfDay().toLong()
+ if (percentage <= maxProgress) {
+ progressText.text = formattedTime
+ progressBar.progress = percentage.toInt()
+ handler.postDelayed(this, delayMillis.toLong())
+ } else {
+ // Reset progress bar and text
+ progressBar.progress = 0
+ progressText.text = "00:00"
+ // Start the loop again
+ handler.postDelayed(this, delayMillis.toLong())
+ }
+ }
}
- return pattern
+ handler.post(updateRunnable)
}
fun getPercentageOfDay(): Float {
@@ -200,39 +200,4 @@ class AlarmActivity : FragmentActivity() {
val percentage = (totalSeconds / secondsInDay) * 100
return percentage.toFloat()
}
-
- fun stopAlarm(view: View?) {
- alarmIsRunning = false
- releaseResources()
- supportFragmentManager.popBackStackImmediate()
- finish()
- }
-
- override fun onResume() {
- super.onResume()
- wakeLock?.acquire()
-
- if (!alarmIsRunning) {
- //alarmIsRunning = true
- }
- }
-
- override fun onPause() {
- super.onPause()
- stopAlarm(null)
- /*alarmIsRunning = false
- mediaPlayer?.stop()
- vibrator?.cancel()*/
- }
-
- private fun releaseResources() {
- vibrator?.cancel()
- mediaPlayer?.stop()
- mediaPlayer?.reset()
- mediaPlayer?.release()
- mediaPlayer = null
-
- wakeLock?.release()
- wakeLock = null
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rooster/rooster/MainActivity.kt b/app/src/main/java/com/rooster/rooster/MainActivity.kt
index 9aac459..740ae06 100644
--- a/app/src/main/java/com/rooster/rooster/MainActivity.kt
+++ b/app/src/main/java/com/rooster/rooster/MainActivity.kt
@@ -65,6 +65,7 @@ class MainActivity() : ComponentActivity() {
Manifest.permission.USE_FULL_SCREEN_INTENT,
Manifest.permission.SYSTEM_ALERT_WINDOW,
Manifest.permission.WAKE_LOCK,
+ Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.SCHEDULE_EXACT_ALARM,
Manifest.permission.FOREGROUND_SERVICE
)
diff --git a/app/src/main/java/com/rooster/rooster/RingtoneActivity.kt b/app/src/main/java/com/rooster/rooster/RingtoneActivity.kt
index 6e778e7..3fbb026 100644
--- a/app/src/main/java/com/rooster/rooster/RingtoneActivity.kt
+++ b/app/src/main/java/com/rooster/rooster/RingtoneActivity.kt
@@ -1,75 +1,61 @@
package com.rooster.rooster
import android.content.Intent
-import android.media.RingtoneManager
import android.net.Uri
import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.app.ActivityCompat
-import androidx.core.content.ContextCompat
-import android.Manifest
-import android.content.pm.PackageManager
import android.util.Log
import android.widget.Toast
class RingtoneActivity : AppCompatActivity() {
private var alarmId: Long = 0
- private val RINGTONE_PICKER_REQUEST = 999
+
+ // Register a callback for the result from opening a document
+ private val openDocumentRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ val uri: Uri? = result.data?.data
+ uri?.let {
+ // Grant temporary read permission to the content URI
+ contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+
+ // Use the Uri to access the selected file here.
+ updateAlarmRingtone(alarmId, it.toString())
+ Toast.makeText(this, "Ringtone file selected!", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ringtone)
- // Check and request permission
- checkPermission()
-
- // Get alarm ID from intent
intent.extras?.let {
alarmId = it.getLong("alarm_id")
Log.i("RingtoneActivity", "Alarm ID: $alarmId")
}
- // Open ringtone picker
- val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
- putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM)
- putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select Alarm Ringtone")
- }
- startActivityForResult(intent, RINGTONE_PICKER_REQUEST)
- }
-
- private fun checkPermission() {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1)
- }
- }
-
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == 1 && grantResults.isNotEmpty() && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
- Toast.makeText(this, "Permission denied to read your External storage", Toast.LENGTH_SHORT).show()
- }
+ // Trigger the document selection
+ selectRingtoneFile()
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (requestCode == RINGTONE_PICKER_REQUEST) {
- val ringtoneUri: Uri? = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
- ringtoneUri?.let {
- // Here you should update the alarm in your database with the URI
- updateAlarmRingtone(alarmId, it.toString())
- Toast.makeText(this, "Ringtone selected!", Toast.LENGTH_SHORT).show()
- }
+ private fun selectRingtoneFile() {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "audio/*" // Filter to show only audio files. Adjust if needed.
}
+ openDocumentRequest.launch(intent)
}
private fun updateAlarmRingtone(alarmId: Long, ringtoneUri: String) {
+ // Update your method to handle the ringtone file URI
val alarmDbHelper = AlarmDbHelper(this)
val alarm = alarmDbHelper.getAlarm(alarmId)
- Log.w("Update", "Ringtone update Intent for alarm $alarmId")
+ Log.w("Update", "Ringtone file URI update Intent for alarm $alarmId")
alarm?.let {
it.ringtoneUri = ringtoneUri
alarmDbHelper.updateAlarm(it)
- Log.i("Update", "Ringtone updated for alarm $alarmId")
+ Log.i("Update", "Ringtone file URI updated for alarm $alarmId")
}
finish()
}