mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add shared calling intent system.
This commit is contained in:
committed by
Cody Henthorne
parent
e5b482c7ad
commit
4d23f11f6e
@@ -506,6 +506,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||
|
||||
sectionHeaderPref(DSLSettingsText.from("Calling options"))
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from("Use new calling UI"),
|
||||
isChecked = state.newCallingUi,
|
||||
onClick = {
|
||||
viewModel.setUseNewCallingUi(!state.newCallingUi)
|
||||
}
|
||||
)
|
||||
|
||||
radioListPref(
|
||||
title = DSLSettingsText.from("Audio processing method"),
|
||||
listItems = CallManager.AudioProcessingMethod.values().map { it.name }.toTypedArray(),
|
||||
|
||||
@@ -24,5 +24,6 @@ data class InternalSettingsState(
|
||||
val pnpInitialized: Boolean,
|
||||
val useConversationItemV2ForMedia: Boolean,
|
||||
val hasPendingOneTimeDonation: Boolean,
|
||||
val hevcEncoding: Boolean
|
||||
val hevcEncoding: Boolean,
|
||||
val newCallingUi: Boolean
|
||||
)
|
||||
|
||||
@@ -165,7 +165,8 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||
pnpInitialized = SignalStore.misc.hasPniInitializedDevices,
|
||||
useConversationItemV2ForMedia = SignalStore.internal.useConversationItemV2Media(),
|
||||
hasPendingOneTimeDonation = SignalStore.inAppPayments.getPendingOneTimeDonation() != null,
|
||||
hevcEncoding = SignalStore.internal.hevcEncoding
|
||||
hevcEncoding = SignalStore.internal.hevcEncoding,
|
||||
newCallingUi = SignalStore.internal.newCallingUi
|
||||
)
|
||||
|
||||
fun onClearOnboardingState() {
|
||||
@@ -176,6 +177,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||
StoryOnboardingDownloadJob.enqueueIfNeeded()
|
||||
}
|
||||
|
||||
fun setUseNewCallingUi(newCallingUi: Boolean) {
|
||||
SignalStore.internal.newCallingUi = newCallingUi
|
||||
refresh()
|
||||
}
|
||||
|
||||
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository)))
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -91,6 +92,7 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||
val callInfoCallbacks = CallInfoCallbacks(this, controlsAndInfoViewModel, compositeDisposable)
|
||||
|
||||
observeCallEvents()
|
||||
viewModel.processCallIntent(CallIntent(intent))
|
||||
|
||||
setContent {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
@@ -179,6 +181,13 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent != null) {
|
||||
viewModel.processCallIntent(CallIntent(intent))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
Log.i(TAG, "onResume")
|
||||
super.onResume()
|
||||
@@ -203,13 +212,9 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO
|
||||
if (enterPipOnResume) {
|
||||
enterPipOnResume = false;
|
||||
enterPipModeIfPossible();
|
||||
if (viewModel.consumeEnterPipOnResume()) {
|
||||
// TODO enterPipModeIfPossible()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.webrtc.v2
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
|
||||
/**
|
||||
* CallIntent wraps an intent inside one of the call activities to allow for easy typed access to the necessary data within it.
|
||||
*/
|
||||
class CallIntent(
|
||||
private val intent: Intent
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CALL_INTENT_PREFIX = "CallIntent"
|
||||
|
||||
private fun getActivityClass(): Class<out Activity> = if (RemoteConfig.newCallUi || SignalStore.internal.newCallingUi) {
|
||||
CallActivity::class.java
|
||||
} else {
|
||||
WebRtcCallActivity::class.java
|
||||
}
|
||||
|
||||
private fun getActionString(action: Action): String {
|
||||
return "$CALL_INTENT_PREFIX.${action.code}"
|
||||
}
|
||||
|
||||
private fun getExtraString(extra: Extra): String {
|
||||
return "$CALL_INTENT_PREFIX.${extra.code}"
|
||||
}
|
||||
}
|
||||
|
||||
val action: Action by lazy { Action.fromIntent(intent) }
|
||||
|
||||
@get:JvmName("shouldEnableVideoIfAvailable")
|
||||
var shouldEnableVideoIfAvailable: Boolean
|
||||
get() = intent.getBooleanExtra(getExtraString(Extra.ENABLE_VIDEO_IF_AVAILABLE), false)
|
||||
set(value) {
|
||||
intent.putExtra(getExtraString(Extra.ENABLE_VIDEO_IF_AVAILABLE), value)
|
||||
}
|
||||
|
||||
val isStartedFromFullScreen: Boolean by lazy { intent.getBooleanExtra(getExtraString(Extra.STARTED_FROM_FULLSCREEN), false) }
|
||||
|
||||
val isStartedFromCallLink: Boolean by lazy { intent.getBooleanExtra(getExtraString(Extra.STARTED_FROM_CALL_LINK), false) }
|
||||
|
||||
@get:JvmName("shouldLaunchInPip")
|
||||
val shouldLaunchInPip: Boolean by lazy { intent.getBooleanExtra(getExtraString(Extra.LAUNCH_IN_PIP), false) }
|
||||
|
||||
override fun toString(): String {
|
||||
return """
|
||||
CallIntent
|
||||
Action - $action
|
||||
Enable video if available? $shouldEnableVideoIfAvailable
|
||||
Started from full screen? $isStartedFromFullScreen
|
||||
Started from call link? $isStartedFromCallLink
|
||||
Launch in pip? $shouldLaunchInPip
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
enum class Action(val code: String) {
|
||||
VIEW(Intent.ACTION_VIEW),
|
||||
ANSWER_AUDIO("ANSWER_ACTION"),
|
||||
ANSWER_VIDEO("ANSWER_VIDEO_ACTION"),
|
||||
DENY("DENY_ACTION"),
|
||||
END_CALL("END_CALL_ACTION");
|
||||
|
||||
companion object {
|
||||
fun fromIntent(intent: Intent): Action {
|
||||
return intent.action?.let { a -> entries.firstOrNull { a == it.code || a == getActionString(it) } } ?: VIEW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class Extra(val code: String) {
|
||||
ENABLE_VIDEO_IF_AVAILABLE("ENABLE_VIDEO_IF_AVAILABLE"),
|
||||
STARTED_FROM_FULLSCREEN("STARTED_FROM_FULLSCREEN"),
|
||||
STARTED_FROM_CALL_LINK("STARTED_FROM_CALL_LINK"),
|
||||
LAUNCH_IN_PIP("LAUNCH_IN_PIP")
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an intent to launch the call screen.
|
||||
*/
|
||||
class Builder(val context: Context) {
|
||||
private val intent = Intent(context, getActivityClass())
|
||||
|
||||
init {
|
||||
withAction(Action.VIEW)
|
||||
}
|
||||
|
||||
fun withAddedIntentFlags(flags: Int): Builder {
|
||||
intent.addFlags(flags)
|
||||
return this
|
||||
}
|
||||
|
||||
fun withIntentFlags(flags: Int): Builder {
|
||||
intent.flags = flags
|
||||
return this
|
||||
}
|
||||
|
||||
fun withAction(action: Action?): Builder {
|
||||
intent.action = action?.let { getActionString(action) }
|
||||
return this
|
||||
}
|
||||
|
||||
fun withEnableVideoIfAvailable(enableVideoIfAvailable: Boolean): Builder {
|
||||
intent.putExtra(getExtraString(Extra.ENABLE_VIDEO_IF_AVAILABLE), enableVideoIfAvailable)
|
||||
return this
|
||||
}
|
||||
|
||||
fun withStartedFromFullScreen(startedFromFullScreen: Boolean): Builder {
|
||||
intent.putExtra(getExtraString(Extra.STARTED_FROM_FULLSCREEN), startedFromFullScreen)
|
||||
return this
|
||||
}
|
||||
|
||||
fun withStartedFromCallLink(startedFromCallLink: Boolean): Builder {
|
||||
intent.putExtra(getExtraString(Extra.STARTED_FROM_CALL_LINK), startedFromCallLink)
|
||||
return this
|
||||
}
|
||||
|
||||
fun withLaunchInPip(launchInPip: Boolean): Builder {
|
||||
intent.putExtra(getExtraString(Extra.LAUNCH_IN_PIP), launchInPip)
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): Intent {
|
||||
return intent
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Presentation logic and state holder for information that was generally done
|
||||
@@ -48,10 +49,18 @@ class CallViewModel(
|
||||
|
||||
private var previousEvent: WebRtcViewModel? = null
|
||||
private var enableVideoIfAvailable = false
|
||||
private var lastProcessedIntentTimestamp = 0L
|
||||
private var enterPipOnResume = false
|
||||
|
||||
private val internalCallScreenState = MutableStateFlow(CallScreenState())
|
||||
val callScreenState: StateFlow<CallScreenState> = internalCallScreenState
|
||||
|
||||
fun consumeEnterPipOnResume(): Boolean {
|
||||
val enter = enterPipOnResume
|
||||
enterPipOnResume = false
|
||||
return enter
|
||||
}
|
||||
|
||||
fun unregisterEventBus() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
@@ -337,4 +346,31 @@ class CallViewModel(
|
||||
AppDependencies.signalCallManager.selectAudioDevice(SignalAudioManager.ChosenAudioDeviceIdentifier(managerDevice))
|
||||
}
|
||||
}
|
||||
|
||||
fun processCallIntent(callIntent: CallIntent) {
|
||||
if (callIntent.action == CallIntent.Action.ANSWER_VIDEO) {
|
||||
enableVideoIfAvailable = true
|
||||
} else if (callIntent.action == CallIntent.Action.ANSWER_AUDIO || callIntent.isStartedFromFullScreen) {
|
||||
enableVideoIfAvailable = false
|
||||
} else {
|
||||
enableVideoIfAvailable = callIntent.shouldEnableVideoIfAvailable
|
||||
callIntent.shouldEnableVideoIfAvailable = false
|
||||
}
|
||||
|
||||
when (callIntent.action) {
|
||||
CallIntent.Action.ANSWER_AUDIO -> startCall(false)
|
||||
CallIntent.Action.ANSWER_VIDEO -> startCall(true)
|
||||
CallIntent.Action.DENY -> deny()
|
||||
CallIntent.Action.END_CALL -> hangup()
|
||||
CallIntent.Action.VIEW -> Unit
|
||||
}
|
||||
|
||||
// Prevents some issues around intent re-use when dealing with picture-in-picture.
|
||||
val now = System.currentTimeMillis()
|
||||
if (now - lastProcessedIntentTimestamp > 1.seconds.inWholeMilliseconds) {
|
||||
enterPipOnResume = callIntent.shouldLaunchInPip
|
||||
}
|
||||
|
||||
lastProcessedIntentTimestamp = now
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user