Notify when calls start to be routed over cellular data.
Only when the device thinks that it's also connected to a WiFi network.
This commit is contained in:
parent
e024541b8a
commit
88b895f5ea
14 changed files with 141 additions and 9 deletions
|
@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
|
|||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls;
|
||||
import org.thoughtcrime.securesms.components.webrtc.WifiToCellularPopupWindow;
|
||||
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
|
||||
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
@ -108,6 +109,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||
|
||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||
private WifiToCellularPopupWindow wifiToCellularPopupWindow;
|
||||
private DeviceOrientationMonitor deviceOrientationMonitor;
|
||||
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
|
@ -300,6 +302,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
callScreen.setControlsListener(new ControlsListener());
|
||||
|
||||
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
|
||||
wifiToCellularPopupWindow = new WifiToCellularPopupWindow(callScreen);
|
||||
}
|
||||
|
||||
private void initializeViewModel(boolean isLandscapeEnabled) {
|
||||
|
@ -375,6 +378,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
|||
videoTooltip.dismiss();
|
||||
videoTooltip = null;
|
||||
}
|
||||
} else if (event instanceof WebRtcCallViewModel.Event.ShowWifiToCellularPopup) {
|
||||
wifiToCellularPopupWindow.show();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown event: " + event);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
|
||||
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil;
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
@ -74,6 +75,7 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
private final Runnable stopOutgoingRingingMode = this::stopOutgoingRingingMode;
|
||||
|
||||
private boolean canDisplayTooltipIfNeeded = true;
|
||||
private boolean canDisplayPopupIfNeeded = true;
|
||||
private boolean hasEnabledLocalVideo = false;
|
||||
private boolean wasInOutgoingRingingMode = false;
|
||||
private long callConnectedTime = -1;
|
||||
|
@ -292,6 +294,13 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
canDisplayTooltipIfNeeded = false;
|
||||
events.setValue(new Event.ShowVideoTooltip());
|
||||
}
|
||||
|
||||
if (canDisplayPopupIfNeeded && webRtcViewModel.isCellularConnection() && NetworkUtil.isConnectedWifi(ApplicationDependencies.getApplication())) {
|
||||
canDisplayPopupIfNeeded = false;
|
||||
events.setValue(new Event.ShowWifiToCellularPopup());
|
||||
} else if (!webRtcViewModel.isCellularConnection()) {
|
||||
canDisplayPopupIfNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
|
@ -481,6 +490,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
|||
public static class DismissVideoTooltip extends Event {
|
||||
}
|
||||
|
||||
public static class ShowWifiToCellularPopup extends Event {
|
||||
}
|
||||
|
||||
public static class StartCall extends Event {
|
||||
private final boolean isVideoCall;
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package org.thoughtcrime.securesms.components.webrtc
|
||||
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.PopupWindow
|
||||
import androidx.core.view.postDelayed
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Popup shown when the device is connected to a WiFi and cellular network, and WiFi is unusable for
|
||||
* RingRTC.
|
||||
*/
|
||||
class WifiToCellularPopupWindow(private val parent: ViewGroup) : PopupWindow(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.wifi_to_cellular_popup, parent, false),
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
) {
|
||||
|
||||
init {
|
||||
animationStyle = R.style.PopupAnimation
|
||||
}
|
||||
|
||||
fun show() {
|
||||
showAtLocation(parent, Gravity.TOP or Gravity.START, 0, 0)
|
||||
VibrateUtil.vibrate(parent.context, VIBRATE_DURATION_MS)
|
||||
contentView.postDelayed(DISPLAY_DURATION_MS) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DISPLAY_DURATION_MS = TimeUnit.SECONDS.toMillis(4)
|
||||
private const val VIBRATE_DURATION_MS = 50
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.webrtc.PeerConnection
|
||||
|
||||
class WebRtcViewModel(state: WebRtcServiceState) {
|
||||
|
||||
|
@ -104,6 +105,20 @@ class WebRtcViewModel(state: WebRtcServiceState) {
|
|||
state.localDeviceState.isMicrophoneEnabled
|
||||
)
|
||||
|
||||
val isCellularConnection: Boolean = when (state.localDeviceState.networkConnectionType) {
|
||||
PeerConnection.AdapterType.UNKNOWN,
|
||||
PeerConnection.AdapterType.ETHERNET,
|
||||
PeerConnection.AdapterType.WIFI,
|
||||
PeerConnection.AdapterType.VPN,
|
||||
PeerConnection.AdapterType.LOOPBACK,
|
||||
PeerConnection.AdapterType.ADAPTER_TYPE_ANY -> false
|
||||
PeerConnection.AdapterType.CELLULAR,
|
||||
PeerConnection.AdapterType.CELLULAR_2G,
|
||||
PeerConnection.AdapterType.CELLULAR_3G,
|
||||
PeerConnection.AdapterType.CELLULAR_4G,
|
||||
PeerConnection.AdapterType.CELLULAR_5G -> true
|
||||
}
|
||||
|
||||
val isRemoteVideoEnabled: Boolean
|
||||
get() = remoteParticipants.any(CallParticipant::isVideoEnabled) || groupState.isNotIdle && remoteParticipants.size > 1
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
|||
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.webrtc.VideoTrack;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
@ -239,6 +240,17 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
|||
return currentState;
|
||||
}
|
||||
|
||||
@Override protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
PeerConnection.AdapterType type = groupCall.getLocalDeviceState().getNetworkRoute().getLocalAdapterType();
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.setNetworkConnectionType(type)
|
||||
.commit()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupCallEnded(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
|
||||
Log.i(tag, "handleGroupCallEnded(): reason: " + groupCallEndReason);
|
||||
|
|
|
@ -49,6 +49,8 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
|
|||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||
|
||||
currentState = super.handleGroupLocalDeviceStateChanged(currentState);
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||
GroupCall.ConnectionState connectionState = device.getConnectionState();
|
||||
|
|
|
@ -42,6 +42,8 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor {
|
|||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||
|
||||
currentState = super.handleGroupLocalDeviceStateChanged(currentState);
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||
|
||||
|
|
|
@ -90,6 +90,8 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
|
|||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||
|
||||
currentState = super.handleGroupLocalDeviceStateChanged(currentState);
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||
|
||||
|
|
|
@ -51,7 +51,6 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState;
|
|||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.BubbleUtil;
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
@ -513,12 +512,7 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
|
|||
}
|
||||
|
||||
@Override public void onNetworkRouteChanged(Remote remote, NetworkRoute networkRoute) {
|
||||
Log.i(TAG, "onNetworkRouteChanged: localAdapterType: " + networkRoute.getLocalAdapterType());
|
||||
try {
|
||||
callManager.updateBandwidthMode(NetworkUtil.getCallingBandwidthMode(context, networkRoute.getLocalAdapterType()));
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Unable to update bandwidth mode on CallManager", e);
|
||||
}
|
||||
process((s, p) -> p.handleNetworkRouteChanged(s, networkRoute));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.signal.ringrtc.CallId;
|
|||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.CallManager.RingUpdate;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.signal.ringrtc.NetworkRoute;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.components.webrtc.EglBaseWrapper;
|
||||
|
@ -547,6 +548,22 @@ public abstract class WebRtcActionProcessor {
|
|||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleNetworkRouteChanged(@NonNull WebRtcServiceState currentState, @NonNull NetworkRoute networkRoute) {
|
||||
Log.i(tag, "onNetworkRouteChanged: localAdapterType: " + networkRoute.getLocalAdapterType());
|
||||
try {
|
||||
webRtcInteractor.getCallManager().updateBandwidthMode(NetworkUtil.getCallingBandwidthMode(context, networkRoute.getLocalAdapterType()));
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to update bandwidth mode on CallManager", e);
|
||||
}
|
||||
|
||||
PeerConnection.AdapterType type = networkRoute.getLocalAdapterType();
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.setNetworkConnectionType(type)
|
||||
.commit()
|
||||
.build();
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleBandwidthModeUpdate(@NonNull WebRtcServiceState currentState) {
|
||||
try {
|
||||
webRtcInteractor.getCallManager().updateBandwidthMode(NetworkUtil.getCallingBandwidthMode(context));
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.service.webrtc.state
|
|||
import org.thoughtcrime.securesms.components.sensors.Orientation
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.webrtc.PeerConnection
|
||||
|
||||
/**
|
||||
* Local device specific state.
|
||||
|
@ -15,7 +16,8 @@ data class LocalDeviceState constructor(
|
|||
var deviceOrientation: Orientation = Orientation.PORTRAIT_BOTTOM_EDGE,
|
||||
var activeDevice: SignalAudioManager.AudioDevice = SignalAudioManager.AudioDevice.NONE,
|
||||
var availableDevices: Set<SignalAudioManager.AudioDevice> = emptySet(),
|
||||
var bluetoothPermissionDenied: Boolean = false
|
||||
var bluetoothPermissionDenied: Boolean = false,
|
||||
var networkConnectionType: PeerConnection.AdapterType = PeerConnection.AdapterType.UNKNOWN,
|
||||
) {
|
||||
|
||||
fun duplicate(): LocalDeviceState {
|
||||
|
|
|
@ -130,6 +130,11 @@ public class WebRtcServiceStateBuilder {
|
|||
toBuild.setBluetoothPermissionDenied(bluetoothPermissionDenied);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceStateBuilder setNetworkConnectionType(@NonNull PeerConnection.AdapterType type) {
|
||||
toBuild.setNetworkConnectionType(type);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class CallSetupStateBuilder {
|
||||
|
|
21
app/src/main/res/layout/wifi_to_cellular_popup.xml
Normal file
21
app/src/main/res/layout/wifi_to_cellular_popup.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="94dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/call_participant_update_window_background"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/WifiToCellularPopupWindow__weak_wifi_switched_to_cellular"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body2" />
|
||||
|
||||
</FrameLayout>
|
|
@ -3539,6 +3539,10 @@
|
|||
<string name="CallParticipant__you_on_another_device">You (on another device)</string>
|
||||
<string name="CallParticipant__s_on_another_device">%1$s (on another device)</string>
|
||||
|
||||
<!-- WifiToCellularPopupWindow -->
|
||||
<!-- Message shown during a call when the WiFi network is unusable, and cellular data starts to be used for the call instead. -->
|
||||
<string name="WifiToCellularPopupWindow__weak_wifi_switched_to_cellular">Weak Wi-Fi. Switched to cellular.</string>
|
||||
|
||||
<!-- DeleteAccountFragment -->
|
||||
<string name="DeleteAccountFragment__deleting_your_account_will">Deleting your account will:</string>
|
||||
<string name="DeleteAccountFragment__enter_your_phone_number">Enter your phone number</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue