Add shared calling intent system.
This commit is contained in:
parent
e5b482c7ad
commit
4d23f11f6e
12 changed files with 277 additions and 90 deletions
|
@ -84,9 +84,10 @@ import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoCont
|
||||||
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel;
|
import org.thoughtcrime.securesms.components.webrtc.controls.ControlsAndInfoViewModel;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.requests.CallLinkIncomingRequestSheet;
|
import org.thoughtcrime.securesms.components.webrtc.requests.CallLinkIncomingRequestSheet;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.v2.CallEvent;
|
|
||||||
import org.thoughtcrime.securesms.components.webrtc.v2.CallPermissionsDialogController;
|
|
||||||
import org.thoughtcrime.securesms.components.webrtc.v2.CallControlsChange;
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallControlsChange;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallEvent;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallPermissionsDialogController;
|
||||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
import org.thoughtcrime.securesms.dependencies.AppDependencies;
|
||||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||||
|
@ -132,23 +133,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
private static final int STANDARD_DELAY_FINISH = 1000;
|
private static final int STANDARD_DELAY_FINISH = 1000;
|
||||||
private static final int VIBRATE_DURATION = 50;
|
private static final int VIBRATE_DURATION = 50;
|
||||||
|
|
||||||
/**
|
|
||||||
* ANSWER the call via voice-only.
|
|
||||||
*/
|
|
||||||
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ANSWER the call via video.
|
|
||||||
*/
|
|
||||||
public static final String ANSWER_VIDEO_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_VIDEO_ACTION";
|
|
||||||
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
|
|
||||||
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
|
||||||
|
|
||||||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
|
||||||
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
|
|
||||||
public static final String EXTRA_STARTED_FROM_CALL_LINK = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_CALL_LINK";
|
|
||||||
public static final String EXTRA_LAUNCH_IN_PIP = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_CALL_LINK";
|
|
||||||
|
|
||||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||||
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
|
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
|
||||||
private CallOverflowPopupWindow callOverflowPopupWindow;
|
private CallOverflowPopupWindow callOverflowPopupWindow;
|
||||||
|
@ -184,7 +168,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
@SuppressLint({ "MissingInflatedId" })
|
@SuppressLint({ "MissingInflatedId" })
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
Log.i(TAG, "onCreate(" + getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
|
CallIntent callIntent = getCallIntent();
|
||||||
|
Log.i(TAG, "onCreate(" + callIntent.isStartedFromFullScreen() + ")");
|
||||||
|
|
||||||
lifecycleDisposable = new LifecycleDisposable();
|
lifecycleDisposable = new LifecycleDisposable();
|
||||||
lifecycleDisposable.bindTo(this);
|
lifecycleDisposable.bindTo(this);
|
||||||
|
@ -214,18 +199,18 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
|
|
||||||
lifecycleDisposable.add(controlsAndInfo);
|
lifecycleDisposable.add(controlsAndInfo);
|
||||||
|
|
||||||
logIntent(getIntent());
|
logIntent(callIntent);
|
||||||
|
|
||||||
if (ANSWER_VIDEO_ACTION.equals(getIntent().getAction())) {
|
if (callIntent.getAction() == CallIntent.Action.ANSWER_VIDEO) {
|
||||||
enableVideoIfAvailable = true;
|
enableVideoIfAvailable = true;
|
||||||
} else if (ANSWER_ACTION.equals(getIntent().getAction()) || getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false)) {
|
} else if (callIntent.getAction() == CallIntent.Action.ANSWER_AUDIO || callIntent.isStartedFromFullScreen()) {
|
||||||
enableVideoIfAvailable = false;
|
enableVideoIfAvailable = false;
|
||||||
} else {
|
} else {
|
||||||
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
enableVideoIfAvailable = callIntent.shouldEnableVideoIfAvailable();
|
||||||
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
|
callIntent.setShouldEnableVideoIfAvailable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
processIntent(getIntent());
|
processIntent(callIntent);
|
||||||
|
|
||||||
registerSystemPipChangeListeners();
|
registerSystemPipChangeListeners();
|
||||||
|
|
||||||
|
@ -302,10 +287,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(Intent intent) {
|
||||||
Log.i(TAG, "onNewIntent(" + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
|
CallIntent callIntent = getCallIntent();
|
||||||
|
Log.i(TAG, "onNewIntent(" + callIntent.isStartedFromFullScreen() + ")");
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
logIntent(intent);
|
logIntent(callIntent);
|
||||||
processIntent(intent);
|
processIntent(callIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -373,6 +359,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NonNull CallIntent getCallIntent() {
|
||||||
|
return new CallIntent(getIntent());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean enterPipModeIfPossible() {
|
private boolean enterPipModeIfPossible() {
|
||||||
if (isSystemPipEnabledAndAvailable()) {
|
if (isSystemPipEnabledAndAvailable()) {
|
||||||
if (viewModel.canEnterPipMode()) {
|
if (viewModel.canEnterPipMode()) {
|
||||||
|
@ -396,26 +386,20 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
return isSystemPipEnabledAndAvailable() && isInPictureInPictureMode();
|
return isSystemPipEnabledAndAvailable() && isInPictureInPictureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logIntent(@NonNull Intent intent) {
|
private void logIntent(@NonNull CallIntent intent) {
|
||||||
Log.d(TAG, "Intent: Action: " + intent.getAction());
|
Log.d(TAG, intent.toString());
|
||||||
Log.d(TAG, "Intent: EXTRA_STARTED_FROM_FULLSCREEN: " + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false));
|
|
||||||
Log.d(TAG, "Intent: EXTRA_ENABLE_VIDEO_IF_AVAILABLE: " + intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false));
|
|
||||||
Log.d(TAG, "Intent: EXTRA_LAUNCH_IN_PIP: " + intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIntent(@NonNull Intent intent) {
|
private void processIntent(@NonNull CallIntent intent) {
|
||||||
if (ANSWER_ACTION.equals(intent.getAction())) {
|
switch (intent.getAction()) {
|
||||||
handleAnswerWithAudio();
|
case ANSWER_AUDIO -> handleAnswerWithAudio();
|
||||||
} else if (ANSWER_VIDEO_ACTION.equals(intent.getAction())) {
|
case ANSWER_VIDEO -> handleAnswerWithVideo();
|
||||||
handleAnswerWithVideo();
|
case DENY -> handleDenyCall();
|
||||||
} else if (DENY_ACTION.equals(intent.getAction())) {
|
case END_CALL -> handleEndCall();
|
||||||
handleDenyCall();
|
|
||||||
} else if (END_CALL_ACTION.equals(intent.getAction())) {
|
|
||||||
handleEndCall();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (System.currentTimeMillis() - lastProcessedIntentTimestamp > TimeUnit.SECONDS.toMillis(1)) {
|
if (System.currentTimeMillis() - lastProcessedIntentTimestamp > TimeUnit.SECONDS.toMillis(1)) {
|
||||||
enterPipOnResume = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false);
|
enterPipOnResume = intent.shouldLaunchInPip();
|
||||||
}
|
}
|
||||||
|
|
||||||
lastProcessedIntentTimestamp = System.currentTimeMillis();
|
lastProcessedIntentTimestamp = System.currentTimeMillis();
|
||||||
|
@ -529,7 +513,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
|
|
||||||
lifecycleDisposable.add(viewModel.getInCallstatus().subscribe(this::handleInCallStatus));
|
lifecycleDisposable.add(viewModel.getInCallstatus().subscribe(this::handleInCallStatus));
|
||||||
|
|
||||||
boolean isStartedFromCallLink = getIntent().getBooleanExtra(WebRtcCallActivity.EXTRA_STARTED_FROM_CALL_LINK, false);
|
boolean isStartedFromCallLink = getCallIntent().isStartedFromCallLink();
|
||||||
LiveDataUtil.combineLatest(LiveDataReactiveStreams.fromPublisher(viewModel.getCallParticipantsState().toFlowable(BackpressureStrategy.LATEST)),
|
LiveDataUtil.combineLatest(LiveDataReactiveStreams.fromPublisher(viewModel.getCallParticipantsState().toFlowable(BackpressureStrategy.LATEST)),
|
||||||
orientationAndLandscapeEnabled,
|
orientationAndLandscapeEnabled,
|
||||||
viewModel.getEphemeralState(),
|
viewModel.getEphemeralState(),
|
||||||
|
|
|
@ -506,6 +506,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
|
||||||
|
|
||||||
sectionHeaderPref(DSLSettingsText.from("Calling options"))
|
sectionHeaderPref(DSLSettingsText.from("Calling options"))
|
||||||
|
|
||||||
|
switchPref(
|
||||||
|
title = DSLSettingsText.from("Use new calling UI"),
|
||||||
|
isChecked = state.newCallingUi,
|
||||||
|
onClick = {
|
||||||
|
viewModel.setUseNewCallingUi(!state.newCallingUi)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
radioListPref(
|
radioListPref(
|
||||||
title = DSLSettingsText.from("Audio processing method"),
|
title = DSLSettingsText.from("Audio processing method"),
|
||||||
listItems = CallManager.AudioProcessingMethod.values().map { it.name }.toTypedArray(),
|
listItems = CallManager.AudioProcessingMethod.values().map { it.name }.toTypedArray(),
|
||||||
|
|
|
@ -24,5 +24,6 @@ data class InternalSettingsState(
|
||||||
val pnpInitialized: Boolean,
|
val pnpInitialized: Boolean,
|
||||||
val useConversationItemV2ForMedia: Boolean,
|
val useConversationItemV2ForMedia: Boolean,
|
||||||
val hasPendingOneTimeDonation: 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,
|
pnpInitialized = SignalStore.misc.hasPniInitializedDevices,
|
||||||
useConversationItemV2ForMedia = SignalStore.internal.useConversationItemV2Media(),
|
useConversationItemV2ForMedia = SignalStore.internal.useConversationItemV2Media(),
|
||||||
hasPendingOneTimeDonation = SignalStore.inAppPayments.getPendingOneTimeDonation() != null,
|
hasPendingOneTimeDonation = SignalStore.inAppPayments.getPendingOneTimeDonation() != null,
|
||||||
hevcEncoding = SignalStore.internal.hevcEncoding
|
hevcEncoding = SignalStore.internal.hevcEncoding,
|
||||||
|
newCallingUi = SignalStore.internal.newCallingUi
|
||||||
)
|
)
|
||||||
|
|
||||||
fun onClearOnboardingState() {
|
fun onClearOnboardingState() {
|
||||||
|
@ -176,6 +177,11 @@ class InternalSettingsViewModel(private val repository: InternalSettingsReposito
|
||||||
StoryOnboardingDownloadJob.enqueueIfNeeded()
|
StoryOnboardingDownloadJob.enqueueIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setUseNewCallingUi(newCallingUi: Boolean) {
|
||||||
|
SignalStore.internal.newCallingUi = newCallingUi
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {
|
class Factory(private val repository: InternalSettingsRepository) : ViewModelProvider.Factory {
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository)))
|
return requireNotNull(modelClass.cast(InternalSettingsViewModel(repository)))
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.webrtc.v2
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -91,6 +92,7 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||||
val callInfoCallbacks = CallInfoCallbacks(this, controlsAndInfoViewModel, compositeDisposable)
|
val callInfoCallbacks = CallInfoCallbacks(this, controlsAndInfoViewModel, compositeDisposable)
|
||||||
|
|
||||||
observeCallEvents()
|
observeCallEvents()
|
||||||
|
viewModel.processCallIntent(CallIntent(intent))
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
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() {
|
override fun onResume() {
|
||||||
Log.i(TAG, "onResume")
|
Log.i(TAG, "onResume")
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
@ -203,13 +212,9 @@ class CallActivity : BaseActivity(), CallControlsCallback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (viewModel.consumeEnterPipOnResume()) {
|
||||||
TODO
|
// TODO enterPipModeIfPossible()
|
||||||
if (enterPipOnResume) {
|
|
||||||
enterPipOnResume = false;
|
|
||||||
enterPipModeIfPossible();
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
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.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage
|
import org.whispersystems.signalservice.api.messages.calls.HangupMessage
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presentation logic and state holder for information that was generally done
|
* Presentation logic and state holder for information that was generally done
|
||||||
|
@ -48,10 +49,18 @@ class CallViewModel(
|
||||||
|
|
||||||
private var previousEvent: WebRtcViewModel? = null
|
private var previousEvent: WebRtcViewModel? = null
|
||||||
private var enableVideoIfAvailable = false
|
private var enableVideoIfAvailable = false
|
||||||
|
private var lastProcessedIntentTimestamp = 0L
|
||||||
|
private var enterPipOnResume = false
|
||||||
|
|
||||||
private val internalCallScreenState = MutableStateFlow(CallScreenState())
|
private val internalCallScreenState = MutableStateFlow(CallScreenState())
|
||||||
val callScreenState: StateFlow<CallScreenState> = internalCallScreenState
|
val callScreenState: StateFlow<CallScreenState> = internalCallScreenState
|
||||||
|
|
||||||
|
fun consumeEnterPipOnResume(): Boolean {
|
||||||
|
val enter = enterPipOnResume
|
||||||
|
enterPipOnResume = false
|
||||||
|
return enter
|
||||||
|
}
|
||||||
|
|
||||||
fun unregisterEventBus() {
|
fun unregisterEventBus() {
|
||||||
EventBus.getDefault().unregister(this)
|
EventBus.getDefault().unregister(this)
|
||||||
}
|
}
|
||||||
|
@ -337,4 +346,31 @@ class CallViewModel(
|
||||||
AppDependencies.signalCallManager.selectAudioDevice(SignalAudioManager.ChosenAudioDeviceIdentifier(managerDevice))
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ public final class InternalValues extends SignalStoreValues {
|
||||||
public static final String FORCE_ENTER_RESTORE_V2_FLOW = "internal.force_enter_restore_v2_flow";
|
public static final String FORCE_ENTER_RESTORE_V2_FLOW = "internal.force_enter_restore_v2_flow";
|
||||||
public static final String WEB_SOCKET_SHADOWING_STATS = "internal.web_socket_shadowing_stats";
|
public static final String WEB_SOCKET_SHADOWING_STATS = "internal.web_socket_shadowing_stats";
|
||||||
public static final String ENCODE_HEVC = "internal.hevc_encoding";
|
public static final String ENCODE_HEVC = "internal.hevc_encoding";
|
||||||
|
public static final String NEW_CALL_UI = "internal.new.call.ui";
|
||||||
|
|
||||||
InternalValues(KeyValueStore store) {
|
InternalValues(KeyValueStore store) {
|
||||||
super(store);
|
super(store);
|
||||||
|
@ -192,6 +193,14 @@ public final class InternalValues extends SignalStoreValues {
|
||||||
return getBoolean(ENCODE_HEVC, false);
|
return getBoolean(ENCODE_HEVC, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setNewCallingUi(boolean enabled) {
|
||||||
|
putBoolean(NEW_CALL_UI, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getNewCallingUi() {
|
||||||
|
return getBoolean(NEW_CALL_UI, false);
|
||||||
|
}
|
||||||
|
|
||||||
public void setLastScrollPosition(int position) {
|
public void setLastScrollPosition(int position) {
|
||||||
putInteger(LAST_SCROLL_POSITION, position);
|
putInteger(LAST_SCROLL_POSITION, position);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import android.telecom.CallAudioState
|
||||||
import android.telecom.Connection
|
import android.telecom.Connection
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent
|
||||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||||
|
@ -66,9 +66,10 @@ class AndroidCallConnection(
|
||||||
if (Permissions.hasAll(context, android.Manifest.permission.RECORD_AUDIO)) {
|
if (Permissions.hasAll(context, android.Manifest.permission.RECORD_AUDIO)) {
|
||||||
AppDependencies.signalCallManager.acceptCall(false)
|
AppDependencies.signalCallManager.acceptCall(false)
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(context, WebRtcCallActivity::class.java)
|
val intent = CallIntent.Builder(context)
|
||||||
intent.action = if (isVideoCall) WebRtcCallActivity.ANSWER_VIDEO_ACTION else WebRtcCallActivity.ANSWER_ACTION
|
.withAddedIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
|
.withAction(if (isVideoCall) CallIntent.Action.ANSWER_VIDEO else CallIntent.Action.ANSWER_AUDIO)
|
||||||
|
.build()
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.signal.ringrtc.PeekInfo;
|
||||||
import org.signal.ringrtc.Remote;
|
import org.signal.ringrtc.Remote;
|
||||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent;
|
||||||
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil;
|
||||||
import org.thoughtcrime.securesms.database.CallLinkTable;
|
import org.thoughtcrime.securesms.database.CallLinkTable;
|
||||||
import org.thoughtcrime.securesms.database.CallTable;
|
import org.thoughtcrime.securesms.database.CallTable;
|
||||||
|
@ -1253,10 +1254,12 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||||
WebRtcViewModel.State callState = s.getCallInfoState().getCallState();
|
WebRtcViewModel.State callState = s.getCallInfoState().getCallState();
|
||||||
|
|
||||||
if (callState.getInOngoingCall()) {
|
if (callState.getInOngoingCall()) {
|
||||||
Intent intent = new Intent(context, WebRtcCallActivity.class);
|
context.startActivity(
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
new CallIntent.Builder(context)
|
||||||
intent.putExtra(WebRtcCallActivity.EXTRA_LAUNCH_IN_PIP, true);
|
.withIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
context.startActivity(intent);
|
.withLaunchInPip(true)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||||
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
import org.thoughtcrime.securesms.calls.links.CallLinks;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.v2.CallActivity;
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallActivity;
|
||||||
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent;
|
||||||
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
import org.thoughtcrime.securesms.conversation.ConversationIntents;
|
||||||
import org.thoughtcrime.securesms.database.CallLinkTable;
|
import org.thoughtcrime.securesms.database.CallLinkTable;
|
||||||
|
@ -397,11 +398,11 @@ public class CommunicationActions {
|
||||||
|
|
||||||
MessageSender.onMessageSent();
|
MessageSender.onMessageSent();
|
||||||
|
|
||||||
Intent activityIntent = new Intent(callContext.getContext(), getCallActivityClass());
|
callContext.startActivity(
|
||||||
|
new CallIntent.Builder(callContext.getContext())
|
||||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
.withIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.build()
|
||||||
callContext.startActivity(activityIntent);
|
);
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
@ -409,13 +410,13 @@ public class CommunicationActions {
|
||||||
private static void startVideoCallInternal(@NonNull CallContext callContext, @NonNull Recipient recipient, boolean fromCallLink) {
|
private static void startVideoCallInternal(@NonNull CallContext callContext, @NonNull Recipient recipient, boolean fromCallLink) {
|
||||||
AppDependencies.getSignalCallManager().startPreJoinCall(recipient);
|
AppDependencies.getSignalCallManager().startPreJoinCall(recipient);
|
||||||
|
|
||||||
Intent activityIntent = new Intent(callContext.getContext(), getCallActivityClass());
|
callContext.startActivity(
|
||||||
|
new CallIntent.Builder(callContext.getContext())
|
||||||
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.withIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
.putExtra(WebRtcCallActivity.EXTRA_ENABLE_VIDEO_IF_AVAILABLE, true)
|
.withEnableVideoIfAvailable(true)
|
||||||
.putExtra(WebRtcCallActivity.EXTRA_STARTED_FROM_CALL_LINK, fromCallLink);
|
.withStartedFromCallLink(fromCallLink)
|
||||||
|
.build()
|
||||||
callContext.startActivity(activityIntent);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleE164Link(Activity activity, String e164) {
|
private static void handleE164Link(Activity activity, String e164) {
|
||||||
|
@ -479,10 +480,6 @@ public class CommunicationActions {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<? extends Activity> getCallActivityClass() {
|
|
||||||
return RemoteConfig.newCallUi() ? CallActivity.class : WebRtcCallActivity.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
private interface CallContext {
|
private interface CallContext {
|
||||||
@NonNull Permissions.PermissionsBuilder getPermissionsBuilder();
|
@NonNull Permissions.PermissionsBuilder getPermissionsBuilder();
|
||||||
void startActivity(@NonNull Intent intent);
|
void startActivity(@NonNull Intent intent);
|
||||||
|
|
|
@ -15,10 +15,9 @@ import androidx.core.app.Person;
|
||||||
import org.signal.core.util.PendingIntentFlags;
|
import org.signal.core.util.PendingIntentFlags;
|
||||||
import org.thoughtcrime.securesms.MainActivity;
|
import org.thoughtcrime.securesms.MainActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.components.webrtc.v2.CallIntent;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.service.webrtc.ActiveCallManager;
|
|
||||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcCallService;
|
import org.thoughtcrime.securesms.service.webrtc.WebRtcCallService;
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||||
|
|
||||||
|
@ -49,13 +48,13 @@ public class CallNotificationBuilder {
|
||||||
|
|
||||||
private enum LaunchCallScreenIntentState {
|
private enum LaunchCallScreenIntentState {
|
||||||
CONTENT(null, 0),
|
CONTENT(null, 0),
|
||||||
AUDIO(WebRtcCallActivity.ANSWER_ACTION, 1),
|
AUDIO(CallIntent.Action.ANSWER_AUDIO, 1),
|
||||||
VIDEO(WebRtcCallActivity.ANSWER_VIDEO_ACTION, 2);
|
VIDEO(CallIntent.Action.ANSWER_VIDEO, 2);
|
||||||
|
|
||||||
final @Nullable String action;
|
final @Nullable CallIntent.Action action;
|
||||||
final int requestCode;
|
final int requestCode;
|
||||||
|
|
||||||
LaunchCallScreenIntentState(@Nullable String action, int requestCode) {
|
LaunchCallScreenIntentState(@Nullable CallIntent.Action action, int requestCode) {
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.requestCode = requestCode;
|
this.requestCode = requestCode;
|
||||||
}
|
}
|
||||||
|
@ -219,17 +218,17 @@ public class CallNotificationBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PendingIntent getActivityPendingIntent(@NonNull Context context, @NonNull LaunchCallScreenIntentState launchCallScreenIntentState) {
|
private static PendingIntent getActivityPendingIntent(@NonNull Context context, @NonNull LaunchCallScreenIntentState launchCallScreenIntentState) {
|
||||||
Intent intent = new Intent(context, WebRtcCallActivity.class);
|
CallIntent.Builder builder = new CallIntent.Builder(context);
|
||||||
intent.setAction(launchCallScreenIntentState.action);
|
builder.withAction(launchCallScreenIntentState.action);
|
||||||
|
|
||||||
if (launchCallScreenIntentState == LaunchCallScreenIntentState.CONTENT) {
|
if (launchCallScreenIntentState == LaunchCallScreenIntentState.CONTENT) {
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
builder.withIntentFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.putExtra(WebRtcCallActivity.EXTRA_STARTED_FROM_FULLSCREEN, launchCallScreenIntentState == LaunchCallScreenIntentState.CONTENT);
|
builder.withStartedFromFullScreen(launchCallScreenIntentState == LaunchCallScreenIntentState.CONTENT);
|
||||||
intent.putExtra(WebRtcCallActivity.EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
|
builder.withEnableVideoIfAvailable(false);
|
||||||
|
|
||||||
return PendingIntent.getActivity(context, launchCallScreenIntentState.requestCode, intent, PendingIntentFlags.updateCurrent());
|
return PendingIntent.getActivity(context, launchCallScreenIntentState.requestCode, builder.build(), PendingIntentFlags.updateCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean deviceVersionSupportsIncomingCallStyle() {
|
private static boolean deviceVersionSupportsIncomingCallStyle() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue