@@ -8,9 +8,14 @@ import android.app.PendingIntent
88import android.content.Context
99import android.content.Intent
1010import android.content.IntentFilter
11+ import android.database.Cursor
12+ import android.media.AudioAttributes
13+ import android.media.AudioFocusRequest
14+ import android.media.AudioManager
1115import android.media.Ringtone
1216import android.media.RingtoneManager
1317import android.net.Uri
18+ import android.os.Build
1419import android.os.Bundle
1520import android.os.SystemClock
1621import android.provider.Settings
@@ -26,12 +31,16 @@ class MainActivity : FlutterActivity() {
2631 companion object {
2732 const val CHANNEL1 = " ulticlock"
2833 const val CHANNEL2 = " timer"
34+ const val CHANNEL3 = " system_ringtones"
2935 const val ACTION_START_FLUTTER_APP = " com.ccextractor.ultimate_alarm_clock"
3036 const val EXTRA_KEY = " alarmRing"
3137 const val ALARM_TYPE = " isAlarm"
3238 private var isAlarm: String? = " true"
3339 val alarmConfig = hashMapOf(" shouldAlarmRing" to false , " alarmIgnore" to false )
3440 private var ringtone: Ringtone ? = null
41+ private var systemRingtone: Ringtone ? = null
42+ private var audioManager: AudioManager ? = null
43+ private var audioFocusRequest: AudioFocusRequest ? = null
3544 }
3645
3746 override fun onCreate (savedInstanceState : Bundle ? ) {
@@ -40,14 +49,16 @@ class MainActivity : FlutterActivity() {
4049 intentFilter.addAction(" com.ccextractor.ultimate_alarm_clock.START_TIMERNOTIF" )
4150 intentFilter.addAction(" com.ccextractor.ultimate_alarm_clock.STOP_TIMERNOTIF" )
4251 context.registerReceiver(TimerNotification (), intentFilter, Context .RECEIVER_EXPORTED )
52+
53+ audioManager = getSystemService(Context .AUDIO_SERVICE ) as AudioManager
4354 }
4455
45-
4656 override fun configureFlutterEngine (@NonNull flutterEngine : FlutterEngine ) {
4757 super .configureFlutterEngine(flutterEngine)
4858 window.addFlags(WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON or WindowManager .LayoutParams .FLAG_SHOW_WHEN_LOCKED or WindowManager .LayoutParams .FLAG_TURN_SCREEN_ON )
4959 var methodChannel1 = MethodChannel (flutterEngine.dartExecutor.binaryMessenger, CHANNEL1 )
5060 var methodChannel2 = MethodChannel (flutterEngine.dartExecutor.binaryMessenger, CHANNEL2 )
61+ var methodChannel3 = MethodChannel (flutterEngine.dartExecutor.binaryMessenger, CHANNEL3 )
5162
5263 val intent = intent
5364
@@ -71,6 +82,35 @@ class MainActivity : FlutterActivity() {
7182 methodChannel1.invokeMethod(" appStartup" , alarmConfig)
7283 alarmConfig[" shouldAlarmRing" ] = false
7384 }
85+
86+ methodChannel3.setMethodCallHandler { call, result ->
87+ when (call.method) {
88+ " getSystemRingtones" -> {
89+ val category = call.argument<String >(" category" ) ? : " alarm"
90+ val ringtones = getSystemRingtones(category)
91+ result.success(ringtones)
92+ }
93+ " playSystemRingtone" -> {
94+ val ringtoneUri = call.argument<String >(" ringtoneUri" )
95+ if (ringtoneUri != null ) {
96+ playSystemRingtone(ringtoneUri)
97+ result.success(null )
98+ } else {
99+ result.error(" INVALID_ARGUMENT" , " Ringtone URI is required" , null )
100+ }
101+ }
102+ " stopSystemRingtone" -> {
103+ stopSystemRingtone()
104+ result.success(null )
105+ }
106+ " testAudio" -> {
107+ testAudioSetup()
108+ result.success(null )
109+ }
110+ else -> result.notImplemented()
111+ }
112+ }
113+
74114 methodChannel2.setMethodCallHandler { call, result ->
75115 if (call.method == " playDefaultAlarm" ) {
76116 playDefaultAlarm(this )
@@ -294,4 +334,226 @@ class MainActivity : FlutterActivity() {
294334 startActivity(intent)
295335 }
296336
337+ private fun getSystemRingtones (category : String ): List <Map <String , String >> {
338+ val ringtonesList = mutableListOf<Map <String , String >>()
339+
340+ val ringtoneType = when (category.lowercase()) {
341+ " alarm" -> RingtoneManager .TYPE_ALARM
342+ " notification" -> RingtoneManager .TYPE_NOTIFICATION
343+ " ringtone" -> RingtoneManager .TYPE_RINGTONE
344+ else -> RingtoneManager .TYPE_ALARM
345+ }
346+
347+ val ringtoneManager = RingtoneManager (this )
348+ ringtoneManager.setType(ringtoneType)
349+
350+ val cursor: Cursor = ringtoneManager.cursor
351+
352+ while (cursor.moveToNext()) {
353+ val title = cursor.getString(RingtoneManager .TITLE_COLUMN_INDEX )
354+ val uri = ringtoneManager.getRingtoneUri(cursor.position)
355+ val id = cursor.getString(RingtoneManager .ID_COLUMN_INDEX )
356+
357+ if (title != null && uri != null ) {
358+ val ringtoneMap = mapOf (
359+ " title" to title,
360+ " uri" to uri.toString(),
361+ " id" to (id ? : " " ),
362+ " category" to category
363+ )
364+ ringtonesList.add(ringtoneMap)
365+ }
366+ }
367+
368+ cursor.close()
369+ return ringtonesList
370+ }
371+
372+ private fun requestAudioFocus (): Boolean {
373+ return if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
374+ val audioAttributes = AudioAttributes .Builder ()
375+ .setUsage(AudioAttributes .USAGE_ALARM )
376+ .setContentType(AudioAttributes .CONTENT_TYPE_SONIFICATION )
377+ .build()
378+
379+ audioFocusRequest = AudioFocusRequest .Builder (AudioManager .AUDIOFOCUS_GAIN_TRANSIENT )
380+ .setAudioAttributes(audioAttributes)
381+ .setAcceptsDelayedFocusGain(false )
382+ .setWillPauseWhenDucked(false )
383+ .build()
384+
385+ audioManager?.requestAudioFocus(audioFocusRequest!! ) == AudioManager .AUDIOFOCUS_REQUEST_GRANTED
386+ } else {
387+ @Suppress(" DEPRECATION" )
388+ audioManager?.requestAudioFocus(
389+ null ,
390+ AudioManager .STREAM_ALARM ,
391+ AudioManager .AUDIOFOCUS_GAIN_TRANSIENT
392+ ) == AudioManager .AUDIOFOCUS_REQUEST_GRANTED
393+ }
394+ }
395+
396+ private fun abandonAudioFocus () {
397+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
398+ audioFocusRequest?.let { audioManager?.abandonAudioFocusRequest(it) }
399+ } else {
400+ @Suppress(" DEPRECATION" )
401+ audioManager?.abandonAudioFocus(null )
402+ }
403+ }
404+
405+ private fun playSystemRingtone (ringtoneUri : String ) {
406+ try {
407+ stopSystemRingtone()
408+
409+
410+ if (! requestAudioFocus()) {
411+ Log .w(" SystemRingtone" , " Could not gain audio focus" )
412+ }
413+
414+ val uri = Uri .parse(ringtoneUri)
415+ systemRingtone = RingtoneManager .getRingtone(this , uri)
416+
417+ if (systemRingtone == null ) {
418+ Log .e(" SystemRingtone" , " Failed to create ringtone from URI: $ringtoneUri " )
419+ playFallbackAlarm()
420+ return
421+ }
422+
423+
424+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
425+ val audioAttributes = AudioAttributes .Builder ()
426+ .setUsage(AudioAttributes .USAGE_ALARM )
427+ .setContentType(AudioAttributes .CONTENT_TYPE_SONIFICATION )
428+ .build()
429+ systemRingtone?.audioAttributes = audioAttributes
430+ systemRingtone?.isLooping = true
431+ }
432+
433+
434+ val alarmVolume = audioManager?.getStreamVolume(AudioManager .STREAM_ALARM ) ? : 0
435+ val maxAlarmVolume = audioManager?.getStreamMaxVolume(AudioManager .STREAM_ALARM ) ? : 1
436+
437+ if (alarmVolume == 0 ) {
438+ // Set to 50% of max volume if currently muted
439+ audioManager?.setStreamVolume(
440+ AudioManager .STREAM_ALARM ,
441+ maxAlarmVolume / 2 ,
442+ 0
443+ )
444+ Log .i(" SystemRingtone" , " Alarm volume was muted, set to 50%" )
445+ }
446+
447+ Log .i(" SystemRingtone" , " Playing system ringtone: $ringtoneUri , Volume: $alarmVolume /$maxAlarmVolume " )
448+ systemRingtone?.play()
449+
450+ } catch (e: Exception ) {
451+ Log .e(" SystemRingtone" , " Error playing system ringtone: ${e.message} " )
452+ playFallbackAlarm()
453+ }
454+ }
455+
456+ private fun playFallbackAlarm () {
457+ try {
458+ val defaultAlarmUri = RingtoneManager .getDefaultUri(RingtoneManager .TYPE_ALARM )
459+ systemRingtone = RingtoneManager .getRingtone(this , defaultAlarmUri)
460+
461+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
462+ val audioAttributes = AudioAttributes .Builder ()
463+ .setUsage(AudioAttributes .USAGE_ALARM )
464+ .setContentType(AudioAttributes .CONTENT_TYPE_SONIFICATION )
465+ .build()
466+ systemRingtone?.audioAttributes = audioAttributes
467+ systemRingtone?.isLooping = true
468+ }
469+
470+ systemRingtone?.play()
471+ Log .i(" SystemRingtone" , " Playing fallback default alarm" )
472+ } catch (fallbackException: Exception ) {
473+ Log .e(" SystemRingtone" , " Fallback alarm also failed: ${fallbackException.message} " )
474+ }
475+ }
476+
477+ private fun stopSystemRingtone () {
478+ try {
479+ systemRingtone?.stop()
480+ systemRingtone = null
481+ abandonAudioFocus()
482+ Log .i(" SystemRingtone" , " Stopped system ringtone and abandoned audio focus" )
483+ } catch (e: Exception ) {
484+ Log .e(" SystemRingtone" , " Error stopping system ringtone: ${e.message} " )
485+ }
486+ }
487+
488+ private fun testAudioSetup () {
489+ Log .i(" SystemRingtone" , " === AUDIO DIAGNOSTICS ===" )
490+
491+
492+ val am = audioManager
493+ if (am == null ) {
494+ Log .e(" SystemRingtone" , " AudioManager is null!" )
495+ return
496+ }
497+
498+ // Check volumes
499+ val alarmVolume = am.getStreamVolume(AudioManager .STREAM_ALARM )
500+ val maxAlarmVolume = am.getStreamMaxVolume(AudioManager .STREAM_ALARM )
501+ val musicVolume = am.getStreamVolume(AudioManager .STREAM_MUSIC )
502+ val maxMusicVolume = am.getStreamMaxVolume(AudioManager .STREAM_MUSIC )
503+ val ringVolume = am.getStreamVolume(AudioManager .STREAM_RING )
504+ val maxRingVolume = am.getStreamMaxVolume(AudioManager .STREAM_RING )
505+
506+ Log .i(" SystemRingtone" , " Alarm Volume: $alarmVolume /$maxAlarmVolume " )
507+ Log .i(" SystemRingtone" , " Music Volume: $musicVolume /$maxMusicVolume " )
508+ Log .i(" SystemRingtone" , " Ring Volume: $ringVolume /$maxRingVolume " )
509+
510+
511+ val ringerMode = am.ringerMode
512+ val ringerModeText = when (ringerMode) {
513+ AudioManager .RINGER_MODE_SILENT -> " SILENT"
514+ AudioManager .RINGER_MODE_VIBRATE -> " VIBRATE"
515+ AudioManager .RINGER_MODE_NORMAL -> " NORMAL"
516+ else -> " UNKNOWN($ringerMode )"
517+ }
518+ Log .i(" SystemRingtone" , " Ringer Mode: $ringerModeText " )
519+
520+
521+ try {
522+ val defaultAlarmUri = RingtoneManager .getDefaultUri(RingtoneManager .TYPE_ALARM )
523+ Log .i(" SystemRingtone" , " Default alarm URI: $defaultAlarmUri " )
524+
525+ val testRingtone = RingtoneManager .getRingtone(this , defaultAlarmUri)
526+ if (testRingtone != null ) {
527+ Log .i(" SystemRingtone" , " Default alarm ringtone created successfully" )
528+
529+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
530+ val audioAttributes = AudioAttributes .Builder ()
531+ .setUsage(AudioAttributes .USAGE_ALARM )
532+ .setContentType(AudioAttributes .CONTENT_TYPE_SONIFICATION )
533+ .build()
534+ testRingtone.audioAttributes = audioAttributes
535+ Log .i(" SystemRingtone" , " Audio attributes set" )
536+ }
537+
538+ requestAudioFocus()
539+ testRingtone.play()
540+ Log .i(" SystemRingtone" , " Test alarm started" )
541+
542+ // Stop after 3 seconds
543+ android.os.Handler (mainLooper).postDelayed({
544+ testRingtone.stop()
545+ abandonAudioFocus()
546+ Log .i(" SystemRingtone" , " Test alarm stopped" )
547+ }, 3000 )
548+
549+ } else {
550+ Log .e(" SystemRingtone" , " Failed to create test ringtone" )
551+ }
552+ } catch (e: Exception ) {
553+ Log .e(" SystemRingtone" , " Test audio failed: ${e.message} " )
554+ }
555+
556+ Log .i(" SystemRingtone" , " === END DIAGNOSTICS ===" )
557+ }
558+
297559}
0 commit comments