From 0a8069ada2b9855ef3c05b3a70ba4df09a7272fe Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:13:48 +0300 Subject: [PATCH] android: Notification sound (#1468) * android: fix full screen call notification (#1466) * android: Closing call means canceling notification too * show full screen call when screen is off OR locked * make notification non-silent and set category * remove call notification category Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * android: Notification sound * Log * Ringtone channel * rename call channel * Non-hideable headsUp notification and reject button * Removed LockScreenCallChannel * call channel name Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../java/chat/simplex/app/model/NtfManager.kt | 54 ++++++++++++------- .../app/views/call/IncomingCallActivity.kt | 33 ++++++------ .../app/src/main/res/values-de/strings.xml | 1 - .../app/src/main/res/values-ru/strings.xml | 1 - .../app/src/main/res/values/strings.xml | 1 - 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt index b90b6cc302..36963e3e4b 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/NtfManager.kt @@ -25,9 +25,9 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS" // DO NOT change notification channel settings / names - const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION" - const val LockScreenCallChannel: String = "chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION" + const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1" const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL" + const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL" const val CallNotificationId: Int = -1 private const val ChatIdKey: String = "chatId" @@ -39,19 +39,21 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference init { manager.createNotificationChannel(NotificationChannel(MessageChannel, generalGetString(R.string.ntf_channel_messages), NotificationManager.IMPORTANCE_HIGH)) - manager.createNotificationChannel(NotificationChannel(LockScreenCallChannel, generalGetString(R.string.ntf_channel_calls_lockscreen), NotificationManager.IMPORTANCE_HIGH)) - manager.createNotificationChannel(callNotificationChannel()) + manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(R.string.ntf_channel_calls))) + // Remove old channels since they can't be edited + manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION") + manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION") } enum class NotificationAction { ACCEPT_CONTACT_REQUEST } - private fun callNotificationChannel(): NotificationChannel { - val callChannel = NotificationChannel(CallChannel, generalGetString(R.string.ntf_channel_calls), NotificationManager.IMPORTANCE_HIGH) + private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel { + val callChannel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH) val attrs = AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .build() val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once) Log.d(TAG,"callNotificationChannel sound: $soundUri") @@ -153,13 +155,14 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference } fun notifyCallInvitation(invitation: RcvCallInvitation) { - if (SimplexApp.context.isAppOnForeground) return val keyguardManager = getKeyguardManager(context) Log.d(TAG, - "notifyCallInvitation pre-requests: device locked ${keyguardManager.isDeviceLocked}, " + + "notifyCallInvitation pre-requests: " + "keyguard locked ${keyguardManager.isKeyguardLocked}, " + - "callOnLockScreen ${appPreferences.callOnLockScreen.get()}" + "callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " + + "onForeground ${SimplexApp.context.isAppOnForeground}" ) + if (SimplexApp.context.isAppOnForeground) return val contactId = invitation.contact.id Log.d(TAG, "notifyCallInvitation $contactId") val image = invitation.contact.image @@ -169,15 +172,17 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference if ((keyguardManager.isKeyguardLocked || screenOff) && appPreferences.callOnLockScreen.get() != CallOnLockScreen.DISABLE) { val fullScreenIntent = Intent(context, IncomingCallActivity::class.java) val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - NotificationCompat.Builder(context, LockScreenCallChannel) + NotificationCompat.Builder(context, CallChannel) .setFullScreenIntent(fullScreenPendingIntent, true) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setSilent(false) } else { val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once) + val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) NotificationCompat.Builder(context, CallChannel) .setContentIntent(chatPendingIntent(OpenChatAction, invitation.contact.id)) .addAction(R.drawable.ntf_icon, generalGetString(R.string.accept), chatPendingIntent(AcceptCallAction, contactId)) + .addAction(R.drawable.ntf_icon, generalGetString(R.string.reject), chatPendingIntent(RejectCallAction, contactId, true)) + .setFullScreenIntent(fullScreenPendingIntent, true) .setSound(soundUri) } val text = generalGetString( @@ -206,8 +211,11 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference .setLargeIcon(largeIcon) .setColor(0x88FFFF) .setAutoCancel(true) + val notification = ntfBuilder.build() + // This makes notification sound and vibration repeat endlessly + notification.flags = notification.flags or NotificationCompat.FLAG_INSISTENT with(NotificationManagerCompat.from(context)) { - notify(CallNotificationId, ntfBuilder.build()) + notify(CallNotificationId, notification) } } @@ -228,16 +236,20 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference } } - private fun chatPendingIntent(intentAction: String, chatId: String? = null): PendingIntent { + private fun chatPendingIntent(intentAction: String, chatId: String? = null, broadcast: Boolean = false): PendingIntent { Log.d(TAG, "chatPendingIntent for $intentAction") val uniqueInt = (System.currentTimeMillis() and 0xfffffff).toInt() - var intent = Intent(context, MainActivity::class.java) + var intent = Intent(context, if (!broadcast) MainActivity::class.java else NtfActionReceiver::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP) .setAction(intentAction) if (chatId != null) intent = intent.putExtra(ChatIdKey, chatId) - return TaskStackBuilder.create(context).run { - addNextIntentWithParentStack(intent) - getPendingIntent(uniqueInt, PendingIntent.FLAG_IMMUTABLE) + return if (!broadcast) { + TaskStackBuilder.create(context).run { + addNextIntentWithParentStack(intent) + getPendingIntent(uniqueInt, PendingIntent.FLAG_IMMUTABLE) + } + } else { + PendingIntent.getBroadcast(SimplexApp.context, uniqueInt, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } } @@ -255,6 +267,12 @@ class NtfManager(val context: Context, private val appPreferences: AppPreference acceptContactRequest(cInfo, SimplexApp.context.chatModel) SimplexApp.context.chatModel.controller.ntfManager.cancelNotificationsForChat(chatId) } + RejectCallAction -> { + val invitation = SimplexApp.context.chatModel.callInvitations[chatId] + if (invitation != null) { + SimplexApp.context.chatModel.callManager.endCall(invitation = invitation) + } + } else -> { Log.e(TAG, "Unknown action. Make sure you provide action from NotificationAction enum") } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt index 7177fde2f1..043e720b77 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt @@ -1,5 +1,6 @@ package chat.simplex.app.views.call +import android.app.Activity import android.app.KeyguardManager import android.content.Context import android.content.Intent @@ -43,8 +44,7 @@ class IncomingCallActivity: ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val activity = this - setContent { IncomingCallActivityView(vm.chatModel, activity) } + setContent { IncomingCallActivityView(vm.chatModel) } unlockForIncomingCall() } @@ -83,11 +83,12 @@ fun getKeyguardManager(context: Context): KeyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager @Composable -fun IncomingCallActivityView(m: ChatModel, activity: IncomingCallActivity) { +fun IncomingCallActivityView(m: ChatModel) { val switchingCall = m.switchingCall.value val invitation = m.activeCallInvitation.value val call = m.activeCall.value val showCallView = m.showCallView.value + val activity = LocalContext.current as Activity LaunchedEffect(invitation, call, switchingCall, showCallView) { if (!switchingCall && invitation == null && (!showCallView || call == null)) { Log.d(TAG, "IncomingCallActivityView: finishing activity") @@ -105,20 +106,23 @@ fun IncomingCallActivityView(m: ChatModel, activity: IncomingCallActivity) { if (invitation != null) IncomingCallAlertView(invitation, m) } } else if (invitation != null) { - IncomingCallLockScreenAlert(invitation, m, activity) + IncomingCallLockScreenAlert(invitation, m) } } } } @Composable -fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel, activity: IncomingCallActivity) { +fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel) { val cm = chatModel.callManager - val cxt = LocalContext.current - val scope = rememberCoroutineScope() - var callOnLockScreen by remember { mutableStateOf(chatModel.controller.appPrefs.callOnLockScreen.get()) } - LaunchedEffect(true) { SoundPlayer.shared.start(cxt, scope, sound = true) } - DisposableEffect(true) { onDispose { SoundPlayer.shared.stop() } } + val callOnLockScreen by remember { mutableStateOf(chatModel.controller.appPrefs.callOnLockScreen.get()) } + val context = LocalContext.current + DisposableEffect(Unit) { + onDispose { + // Cancel notification whatever happens next since otherwise sound from notification and from inside the app can co-exist + chatModel.controller.ntfManager.cancelCallNotification() + } + } IncomingCallLockScreenAlertLayout( invitation, callOnLockScreen, @@ -129,15 +133,14 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo }, acceptCall = { cm.acceptIncomingCall(invitation = invitation) }, openApp = { - SoundPlayer.shared.stop() - var intent = Intent(activity, MainActivity::class.java) + val intent = Intent(context, MainActivity::class.java) .setAction(OpenChatAction) .putExtra("chatId", invitation.contact.id) - activity.startActivity(intent) - activity.finish() + context.startActivity(intent) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - getKeyguardManager(activity).requestDismissKeyguard(activity, null) + getKeyguardManager(context).requestDismissKeyguard((context as Activity), null) } + (context as Activity).finish() } ) } diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index f38684e53c..a42701dc62 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -115,7 +115,6 @@ SimpleX Chat Nachrichten SimpleX Chat Anrufe - SimpleX Chat Anrufe (Sperrbildschirm) Benachrichtigungsdienst diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index f0a31b831e..4602ee501b 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -115,7 +115,6 @@ SimpleX Chat сообщения SimpleX Chat звонки - SimpleX Chat звонки (экран блокировки) Сервис уведомлений diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index e8aeb00267..b5b97611a7 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -115,7 +115,6 @@ SimpleX Chat messages SimpleX Chat calls - SimpleX Chat calls (lock screen) Notification service