@@ -2,10 +2,7 @@ package network.columba.app
22
33import android.content.Context
44import android.content.Intent
5- import android.media.AudioAttributes
65import android.media.AudioManager
7- import android.media.Ringtone
8- import android.media.RingtoneManager
96import android.os.Build
107import android.os.Bundle
118import android.os.VibrationEffect
@@ -33,10 +30,7 @@ import network.columba.app.notifications.CallNotificationHelper
3330import network.columba.app.repository.SettingsRepository
3431import network.columba.app.ui.screens.IncomingCallActivityScreen
3532import network.columba.app.ui.theme.ThemeMode
36- import kotlinx.coroutines.Job
3733import dagger.hilt.android.EntryPointAccessors
38- import kotlinx.coroutines.delay
39- import kotlinx.coroutines.isActive
4034import kotlinx.coroutines.launch
4135import network.columba.app.di.RnsTelephonyEntryPoint
4236import network.columba.app.rns.api.RnsTelephony
@@ -53,7 +47,7 @@ import network.columba.app.rns.api.model.CallState
5347 * - Shows over lock screen (showWhenLocked / FLAG_SHOW_WHEN_LOCKED)
5448 * - Turns screen on (turnScreenOn / FLAG_TURN_SCREEN_ON)
5549 * - Dismisses keyguard when answering
56- * - Plays default ringtone and vibrates in a call pattern
50+ * - Vibrates in a call pattern (the ringtone is owned by the :reticulum service process)
5751 * - Retrieves the singleton [RnsTelephony] via Hilt's
5852 * [RnsTelephonyEntryPoint] (the Activity itself is kept Hilt-free so
5953 * cold start stays under the lock-screen ringing latency budget — see
@@ -75,8 +69,6 @@ class IncomingCallActivity : ComponentActivity() {
7569 .fromApplication(applicationContext, RnsTelephonyEntryPoint ::class .java)
7670 .settingsRepository()
7771 }
78- private var ringtone: Ringtone ? = null
79- private var ringtoneLoopJob: Job ? = null
8072 private var vibrator: Vibrator ? = null
8173
8274 // Compose-observable state so UI updates when onNewIntent delivers a new call
@@ -100,8 +92,11 @@ class IncomingCallActivity : ComponentActivity() {
10092 // Show over lock screen and turn screen on
10193 configureWindowForIncomingCall()
10294
103- // Start ringtone and vibration
104- startRingtoneAndVibration()
95+ // Vibrate in a phone-call pattern. The ringtone itself is played by the
96+ // :reticulum service process (LXST Telephone) which owns the call
97+ // lifecycle; playing a second ringtone here would double-ring while the
98+ // call screen is up.
99+ startVibration()
105100
106101 enableEdgeToEdge()
107102
@@ -160,7 +155,7 @@ class IncomingCallActivity : ComponentActivity() {
160155 }
161156
162157 override fun onDestroy () {
163- stopRingtoneAndVibration ()
158+ stopVibration ()
164159 super .onDestroy()
165160 }
166161
@@ -173,9 +168,9 @@ class IncomingCallActivity : ComponentActivity() {
173168 if (newHash != null ) {
174169 currentIdentityHash.value = newHash
175170 currentCallerName.value = newName
176- // Restart ringtone/ vibration for the new call
177- stopRingtoneAndVibration ()
178- startRingtoneAndVibration ()
171+ // Restart vibration for the new call
172+ stopVibration ()
173+ startVibration ()
179174 }
180175 }
181176
@@ -204,47 +199,15 @@ class IncomingCallActivity : ComponentActivity() {
204199 }
205200
206201 /* *
207- * Start playing the default ringtone and vibrating in a phone-call pattern.
208- * Respects the device's ringer mode (silent/vibrate/normal).
202+ * Vibrate in a phone-call pattern. Respects the device's ringer mode (no
203+ * vibration in silent mode). The ringtone itself is played by the
204+ * :reticulum service process (LXST Telephone), which owns the call
205+ * lifecycle and stops it on answer/hangup.
209206 */
210- private fun startRingtoneAndVibration () {
207+ private fun startVibration () {
211208 val audioManager = getSystemService(Context .AUDIO_SERVICE ) as AudioManager
212209 val ringerMode = audioManager.ringerMode
213210
214- // Play ringtone (only if not in silent/vibrate mode)
215- if (ringerMode == AudioManager .RINGER_MODE_NORMAL ) {
216- try {
217- val ringtoneUri = RingtoneManager .getDefaultUri(RingtoneManager .TYPE_RINGTONE )
218- ringtone =
219- RingtoneManager .getRingtone(this , ringtoneUri)?.apply {
220- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
221- isLooping = true
222- }
223- audioAttributes =
224- AudioAttributes
225- .Builder ()
226- .setUsage(AudioAttributes .USAGE_NOTIFICATION_RINGTONE )
227- .setContentType(AudioAttributes .CONTENT_TYPE_SONIFICATION )
228- .build()
229- play()
230- }
231- // On pre-P devices, isLooping is not available; manually restart
232- if (Build .VERSION .SDK_INT < Build .VERSION_CODES .P && ringtone != null ) {
233- ringtoneLoopJob =
234- lifecycleScope.launch {
235- val rt = ringtone ? : return @launch
236- while (isActive) {
237- delay(1000 )
238- if (! rt.isPlaying) rt.play()
239- }
240- }
241- }
242- Log .d(TAG , " Ringtone started" )
243- } catch (e: Exception ) {
244- Log .e(TAG , " Error starting ringtone" , e)
245- }
246- }
247-
248211 // Vibrate (in normal or vibrate mode, not silent)
249212 if (ringerMode != AudioManager .RINGER_MODE_SILENT ) {
250213 try {
@@ -275,18 +238,9 @@ class IncomingCallActivity : ComponentActivity() {
275238 }
276239
277240 /* *
278- * Stop ringtone and vibration.
241+ * Stop vibration.
279242 */
280- private fun stopRingtoneAndVibration () {
281- ringtoneLoopJob?.cancel()
282- ringtoneLoopJob = null
283- try {
284- ringtone?.stop()
285- ringtone = null
286- Log .d(TAG , " Ringtone stopped" )
287- } catch (e: Exception ) {
288- Log .e(TAG , " Error stopping ringtone" , e)
289- }
243+ private fun stopVibration () {
290244 try {
291245 vibrator?.cancel()
292246 vibrator = null
@@ -306,7 +260,7 @@ class IncomingCallActivity : ComponentActivity() {
306260 */
307261 private fun answerCall () {
308262 Log .i(TAG , " Answering call" )
309- stopRingtoneAndVibration ()
263+ stopVibration ()
310264 dismissKeyguardAndAnswer()
311265 }
312266
@@ -358,7 +312,7 @@ class IncomingCallActivity : ComponentActivity() {
358312 */
359313 private fun declineCall () {
360314 Log .i(TAG , " Declining call" )
361- stopRingtoneAndVibration ()
315+ stopVibration ()
362316 lifecycleScope.launch {
363317 try {
364318 telephony.declineCall()
0 commit comments