Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Minimise incoming/outgoing call screen (WPB-979) #2912

Merged
merged 51 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ff32e24
refactor: move calling feature a separate activity
ohassine Apr 9, 2024
eabcf67
chore: unit test
ohassine Apr 10, 2024
e539911
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 10, 2024
f5089e3
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 10, 2024
2fdcf1f
chore: unit test
ohassine Apr 10, 2024
c3473f0
chore: detekt
ohassine Apr 10, 2024
eaa8508
chore: detekt
ohassine Apr 10, 2024
fba80ca
chore: cleanup
ohassine Apr 11, 2024
a468ef2
Merge branch 'develop' into move_calling_to_a_separate_activity
vitorhugods Apr 11, 2024
48ea54b
fix: adjust calling activity flags
ohassine Apr 11, 2024
cf6836d
chore: detekt
ohassine Apr 12, 2024
db4e3d5
Merge branch 'develop' into move_calling_to_a_separate_activity
ohassine Apr 12, 2024
a901798
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 12, 2024
a595d66
Merge branch 'develop' into move_calling_to_a_separate_activity
ohassine Apr 12, 2024
223811e
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 16, 2024
8deaf2a
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 16, 2024
5e267db
feat: add correct activity flags for callingActivity
ohassine Apr 16, 2024
2732ea0
fix: replace deprecated flags
ohassine Apr 16, 2024
db2c6bf
chore: address comments
ohassine Apr 17, 2024
b637415
Merge branch 'develop' into move_calling_to_a_separate_activity
ohassine Apr 17, 2024
cdbbd52
chore: unit test
ohassine Apr 17, 2024
e5df824
Merge remote-tracking branch 'origin/move_calling_to_a_separate_activ…
ohassine Apr 17, 2024
bd8946c
Merge remote-tracking branch 'origin/develop' into move_calling_to_a_…
ohassine Apr 17, 2024
a6e3951
feat: minimise incoming and outgoing call screens
ohassine Apr 18, 2024
ae1b4e8
chore: update kalium reference
ohassine Apr 18, 2024
1daef2c
chore: resolve conflicts
ohassine Apr 18, 2024
caf3cdb
chore: detekt
ohassine Apr 18, 2024
70b65a0
Merge branch 'develop' into minimise-calling-screens
ohassine Apr 18, 2024
08f193d
chore: revert changes
ohassine Apr 18, 2024
c5ab04b
chore: cover CommonTopAppBarViewModel with test
ohassine Apr 19, 2024
8564f2d
chore: cover WireNotificationManager with test
ohassine Apr 19, 2024
2812f5b
chore: detekt
ohassine Apr 19, 2024
5e2b476
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 19, 2024
741becc
chore: cover CallNotificationManager with unit test
ohassine Apr 22, 2024
f1961fe
chore: detekt
ohassine Apr 22, 2024
6108386
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 23, 2024
52e4186
chore: address comments
ohassine Apr 25, 2024
269ee40
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 25, 2024
0ca1f7a
chore: kalium reference
ohassine Apr 25, 2024
a478a5a
chore: resolve conflicts
ohassine Apr 25, 2024
b28a4fa
Merge branch 'develop' into minimise-calling-screens
ohassine Apr 25, 2024
2544ef7
chore: detekt
ohassine Apr 25, 2024
8d7782f
Merge remote-tracking branch 'origin/minimise-calling-screens' into m…
ohassine Apr 25, 2024
b383fe1
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 25, 2024
ecd27de
chore: kalium reference
ohassine Apr 25, 2024
6ffb940
Update app/src/test/kotlin/com/wire/android/notification/CallNotifica…
ohassine Apr 26, 2024
528e030
chore: missing unit test
ohassine Apr 29, 2024
854bfd2
Merge remote-tracking branch 'origin/minimise-calling-screens' into m…
ohassine Apr 29, 2024
3488534
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 29, 2024
a7e636a
Merge remote-tracking branch 'origin/develop' into minimise-calling-s…
ohassine Apr 29, 2024
5f515ed
chore: update kalium reference
ohassine Apr 29, 2024
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
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<activity
android:name=".ui.calling.CallActivity"
android:exported="true"
android:launchMode="singleTop"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:theme="@style/AppTheme" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.wire.kalium.logic.feature.call.usecase.FlipToFrontCameraUseCase
import com.wire.kalium.logic.feature.call.usecase.GetAllCallsWithSortedParticipantsUseCase
import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveSpeakerUseCase
import com.wire.kalium.logic.feature.call.usecase.SetVideoPreviewUseCase
import com.wire.kalium.logic.feature.call.usecase.StartCallUseCase
Expand Down Expand Up @@ -98,6 +99,13 @@ class CallsModule {
): ObserveEstablishedCallsUseCase =
callsScope.establishedCall

@ViewModelScoped
@Provides
fun provideObserveOutgoingCallUseCase(
callsScope: CallsScope
): ObserveOutgoingCallUseCase =
callsScope.observeOutgoingCall

@ViewModelScoped
@Provides
fun provideStartCallUseCase(callsScope: CallsScope): StartCallUseCase =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.wire.android.appLogger
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.data.call.Call
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.user.UserId
import kotlinx.coroutines.CoroutineScope
Expand All @@ -53,6 +54,7 @@ class CallNotificationManager @Inject constructor(
private val notificationManager = NotificationManagerCompat.from(context)
private val scope = CoroutineScope(SupervisorJob() + dispatcherProvider.default())
private val incomingCallsForUsers = MutableStateFlow<List<Pair<UserId, Call>>>(emptyList())
private val outgoingCallForUsers = MutableStateFlow<Map<UserId, Call>>(mapOf())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use persistentMap?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of persistentMap ?


init {
scope.launch {
Expand All @@ -70,15 +72,41 @@ class CallNotificationManager @Inject constructor(
}
}
}
scope.launch {
outgoingCallForUsers.collectLatest {
if (it.isEmpty()) {
hideOutgoingCallNotification()
} else {
val call = it.values.first()
showOutgoingCallNotification(
conversationId = call.conversationId,
userId = it.keys.first(),
conversationName = call.conversationName
?: context.getString(R.string.calling_participant_tile_default_user_name)
)
}
}
}
}

fun handleIncomingCallNotifications(calls: List<Call>, userId: UserId) {
if (calls.isEmpty()) {
incomingCallsForUsers.update { it.filter { it.first != userId } }
incomingCallsForUsers.update {
it.filter {
it.first != userId
}
}
} else {
incomingCallsForUsers.update { it.filter { it.first != userId } + (userId to calls.first()) }
}
}
fun handleOutgoingCallNotifications(calls: List<Call>, userId: UserId) {
if (calls.isEmpty()) {
outgoingCallForUsers.update { it.filter { it.key != userId } }
} else {
outgoingCallForUsers.update { it.filter { it.key != userId } + (userId to calls.first()) }
}
}

fun hideAllNotifications() {
hideIncomingCallNotification()
Expand All @@ -94,12 +122,34 @@ class CallNotificationManager @Inject constructor(
TimeUnit.MILLISECONDS.sleep(CANCEL_CALL_NOTIFICATION_DELAY)
notificationManager.cancel(NotificationConstants.CALL_INCOMING_NOTIFICATION_ID)
}
private fun hideOutgoingCallNotification() {
appLogger.i("$TAG: hiding outgoing call")
notificationManager.cancel(NotificationConstants.CALL_OUTGOING_NOTIFICATION_ID)
}

@VisibleForTesting
internal fun showIncomingCallNotification(call: Call, userId: QualifiedID) {
appLogger.i("$TAG: showing incoming call for user ${userId.toLogString()}")
appLogger.i("$TAG: showing incoming call notification for user ${userId.toLogString()}")
val notification = builder.getIncomingCallNotification(call, userId)
notificationManager.notify(NotificationConstants.CALL_INCOMING_NOTIFICATION_ID, notification)
notificationManager.notify(
NotificationConstants.CALL_INCOMING_NOTIFICATION_ID,
notification
)
}

@VisibleForTesting
internal fun showOutgoingCallNotification(
conversationId: ConversationId,
userId: UserId,
conversationName: String
) {
appLogger.i("$TAG: showing outgoing call notification for user ${userId.toLogString()}")
val notification =
builder.getOutgoingCallNotification(conversationId, userId, conversationName)
notificationManager.notify(
NotificationConstants.CALL_OUTGOING_NOTIFICATION_ID,
notification
)
}

// Notifications
Expand All @@ -120,6 +170,38 @@ class CallNotificationManager @Inject constructor(
class CallNotificationBuilder @Inject constructor(
private val context: Context,
) {

fun getOutgoingCallNotification(
conversationId: ConversationId,
userId: UserId,
conversationName: String
): Notification {
val conversationIdString = conversationId.toString()
val channelId = NotificationConstants.getOutgoingChannelId(userId)

return NotificationCompat.Builder(context, channelId)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_CALL)
.setSmallIcon(R.drawable.notification_icon_small)
.setContentTitle(conversationName)
.setContentText(context.getString(R.string.notification_outgoing_call_tap_to_return))
.setAutoCancel(false)
.setOngoing(true)
.setFullScreenIntent(
outgoingCallPendingIntent(context, conversationIdString),
true
)
.addAction(getHangUpCallAction(context, conversationIdString, userId.toString()))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(
outgoingCallPendingIntent(
context,
conversationIdString,
)
)
.build()
}

fun getIncomingCallNotification(call: Call, userId: QualifiedID): Notification {
val conversationIdString = call.conversationId.toString()
val userIdString = userId.toString()
Expand All @@ -133,16 +215,14 @@ class CallNotificationBuilder @Inject constructor(
.setSmallIcon(R.drawable.notification_icon_small)
.setContentTitle(title)
.setContentText(content)
.setAutoCancel(true)
.setAutoCancel(false)
.setOngoing(true)
.setVibrate(VIBRATE_PATTERN)
.setTimeoutAfter(INCOMING_CALL_TIMEOUT)
.setFullScreenIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString, userIdString), true)
.setFullScreenIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString), true)
.addAction(getDeclineCallAction(context, conversationIdString, userIdString))
.addAction(getOpenIncomingCallAction(context, conversationIdString, userIdString))
.addAction(getOpenIncomingCallAction(context, conversationIdString))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString, userIdString))
.setDeleteIntent(declineCallPendingIntent(context, conversationIdString, userIdString))
.setContentIntent(fullScreenIncomingCallPendingIntent(context, conversationIdString))
.build()

// Added FLAG_INSISTENT so the ringing sound repeats itself until an action is done.
Expand Down Expand Up @@ -210,7 +290,6 @@ class CallNotificationBuilder @Inject constructor(
}

companion object {
private const val INCOMING_CALL_TIMEOUT: Long = 30 * 1000
private val VIBRATE_PATTERN = longArrayOf(0, 1000, 1000)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ fun getActionReply(
}
}

fun getOpenIncomingCallAction(context: Context, conversationId: String, userId: String) = getAction(
fun getOpenIncomingCallAction(context: Context, conversationId: String) = getAction(
context.getString(R.string.notification_action_open_call),
fullScreenIncomingCallPendingIntent(context, conversationId, userId)
fullScreenIncomingCallPendingIntent(context, conversationId)
)

fun getDeclineCallAction(context: Context, conversationId: String, userId: String) = getAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ class NotificationChannelsManager @Inject constructor(
)
}

private val outgoingCallSoundUri by lazy {
Uri.parse(
"${ContentResolver.SCHEME_ANDROID_RESOURCE}://" +
"${context.packageName}/raw/ringing_from_me"
)
}

private val knockSoundUri by lazy {
Uri.parse(
"${ContentResolver.SCHEME_ANDROID_RESOURCE}://" +
Expand All @@ -64,6 +71,7 @@ class NotificationChannelsManager @Inject constructor(
val groupId = createNotificationChannelGroup(user.id, user.handle ?: user.name ?: user.id.value)

createIncomingCallsChannel(groupId, user.id)
createOutgoingCallChannel(groupId, user.id)
createMessagesNotificationChannel(user.id, groupId)
createPingNotificationChannel(user.id, groupId)
}
Expand Down Expand Up @@ -122,6 +130,26 @@ class NotificationChannelsManager @Inject constructor(
notificationManagerCompat.createNotificationChannel(notificationChannel)
}

private fun createOutgoingCallChannel(groupId: String, userId: UserId) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(getAudioAttributeUsageByOsLevel())
.build()

val channelId = NotificationConstants.getOutgoingChannelId(userId)
val notificationChannel = NotificationChannelCompat
.Builder(channelId, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(NotificationConstants.OUTGOING_CALL_CHANNEL_NAME)
.setImportance(NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setSound(outgoingCallSoundUri, audioAttributes)
.setShowBadge(false)
.setVibrationPattern(VIBRATE_PATTERN)
.setGroup(groupId)
.build()

notificationManagerCompat.createNotificationChannel(notificationChannel)
}

private fun createOngoingNotificationChannel() {
val channelId = NotificationConstants.ONGOING_CALL_CHANNEL_ID
val notificationChannel = NotificationChannelCompat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import com.wire.kalium.logic.data.user.UserId
object NotificationConstants {

private const val INCOMING_CALL_CHANNEL_ID = "com.wire.android.notification_incoming_call_channel"
private const val OUTGOING_CALL_CHANNEL_ID = "com.wire.android.notification_outgoing_call_channel"
const val INCOMING_CALL_CHANNEL_NAME = "Incoming calls"
const val OUTGOING_CALL_CHANNEL_NAME = "Outgoing call"
const val ONGOING_CALL_CHANNEL_ID = "com.wire.android.notification_ongoing_call_channel"
const val ONGOING_CALL_CHANNEL_NAME = "Ongoing calls"

Expand All @@ -49,6 +51,7 @@ object NotificationConstants {

// Notification IDs (has to be unique!)
val CALL_INCOMING_NOTIFICATION_ID = "wire_incoming_call_notification".hashCode()
val CALL_OUTGOING_NOTIFICATION_ID = "wire_outgoing_call_notification".hashCode()
val CALL_ONGOING_NOTIFICATION_ID = "wire_ongoing_call_notification".hashCode()
val PERSISTENT_NOTIFICATION_ID = "wire_persistent_web_socket_notification".hashCode()
val MESSAGE_SYNC_NOTIFICATION_ID = "wire_notification_fetch_notification".hashCode()
Expand All @@ -66,6 +69,7 @@ object NotificationConstants {
fun getMessagesChannelId(userId: UserId): String = getChanelIdForUser(userId, MESSAGE_CHANNEL_ID)
fun getPingsChannelId(userId: UserId): String = getChanelIdForUser(userId, PING_CHANNEL_ID)
fun getIncomingChannelId(userId: UserId): String = getChanelIdForUser(userId, INCOMING_CALL_CHANNEL_ID)
fun getOutgoingChannelId(userId: UserId): String = getChanelIdForUser(userId, OUTGOING_CALL_CHANNEL_ID)

/**
* @return NotificationChannelId [String] specific for user, use it to post a notifications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.wire.android.notification.broadcastreceivers.NotificationReplyReceive
import com.wire.android.ui.WireActivity
import com.wire.android.ui.calling.CallActivity
import com.wire.android.ui.calling.CallScreenType
import com.wire.android.ui.calling.getIncomingCallIntent
import com.wire.android.util.deeplink.DeepLinkProcessor

fun messagePendingIntent(context: Context, conversationId: String, userId: String?): PendingIntent {
Expand Down Expand Up @@ -116,8 +117,19 @@ fun declineCallPendingIntent(context: Context, conversationId: String, userId: S
)
}

fun fullScreenIncomingCallPendingIntent(context: Context, conversationId: String, userId: String): PendingIntent {
val intent = openIncomingCallIntent(context, conversationId, userId)
fun outgoingCallPendingIntent(context: Context, conversationId: String): PendingIntent {
val intent = openOutgoingCallIntent(context, conversationId)

return PendingIntent.getActivity(
context,
OUTGOING_CALL_REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}

fun fullScreenIncomingCallPendingIntent(context: Context, conversationId: String): PendingIntent {
val intent = getIncomingCallIntent(context, conversationId)

return PendingIntent.getActivity(
context,
Expand All @@ -127,11 +139,10 @@ fun fullScreenIncomingCallPendingIntent(context: Context, conversationId: String
)
}

private fun openIncomingCallIntent(context: Context, conversationId: String, userId: String) =
private fun openOutgoingCallIntent(context: Context, conversationId: String) =
Intent(context.applicationContext, CallActivity::class.java).apply {
putExtra(CallActivity.EXTRA_CONVERSATION_ID, conversationId)
putExtra(CallActivity.EXTRA_USER_ID, userId)
putExtra(CallActivity.EXTRA_SCREEN_TYPE, CallScreenType.Incoming.name)
putExtra(CallActivity.EXTRA_SCREEN_TYPE, CallScreenType.Outgoing.name)
}

private fun openOngoingCallIntent(context: Context, conversationId: String) =
Expand Down Expand Up @@ -176,6 +187,7 @@ private const val DECLINE_CALL_REQUEST_CODE = "decline_call_"
private const val FULL_SCREEN_REQUEST_CODE = 3
private const val OPEN_ONGOING_CALL_REQUEST_CODE = 4
private const val OPEN_MIGRATION_LOGIN_REQUEST_CODE = 5
private const val OUTGOING_CALL_REQUEST_CODE = 6
private const val END_ONGOING_CALL_REQUEST_CODE = "hang_up_call_"
private const val OPEN_MESSAGE_REQUEST_CODE_PREFIX = "open_message_"
private const val OPEN_OTHER_USER_PROFILE_CODE_PREFIX = "open_other_user_profile_"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ class WireNotificationManager @Inject constructor(
incomingCallsJob = scope.launch(dispatcherProvider.default()) {
observeIncomingCalls(userId)
},
outgoingCallJob = scope.launch(dispatcherProvider.default()) {
observeOutgoingCalls(userId)
},
messagesJob = scope.launch(dispatcherProvider.default()) {
observeMessageNotifications(userId, currentScreenState)
},
Expand Down Expand Up @@ -336,6 +339,15 @@ class WireNotificationManager @Inject constructor(
}
}

private suspend fun observeOutgoingCalls(
userId: UserId
) {
appLogger.d("$TAG observing outgoing calls")
coreLogic.getSessionScope(userId).calls.observeOutgoingCall().collect {
callNotificationManager.handleOutgoingCallNotifications(it, userId)
}
}

/**
* Infinitely listen for the new Message notifications and show it.
* Can be used for listening for the Notifications when the app is running.
Expand Down Expand Up @@ -489,16 +501,18 @@ class WireNotificationManager @Inject constructor(
private data class UserObservingJobs(
val currentScreenJob: Job,
val incomingCallsJob: Job,
val outgoingCallJob: Job,
val messagesJob: Job,
) {
fun cancelAll() {
currentScreenJob.cancel()
incomingCallsJob.cancel()
outgoingCallJob.cancel()
messagesJob.cancel()
}

fun isAllActive(): Boolean =
currentScreenJob.isActive && incomingCallsJob.isActive && messagesJob.isActive
currentScreenJob.isActive && incomingCallsJob.isActive && messagesJob.isActive && outgoingCallJob.isActive
}

private data class ObservingJobs(
Expand Down
Loading
Loading