Handle bluetooth permission crash during calls.

This commit is contained in:
Cody Henthorne 2022-05-13 12:39:23 -04:00 committed by GitHub
parent 97d41fdd1e
commit a3bbf944e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 2 deletions

View file

@ -44,6 +44,8 @@ import androidx.window.DisplayFeature;
import androidx.window.FoldingFeature;
import androidx.window.WindowLayoutInfo;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@ -112,6 +114,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private TooltipPopup videoTooltip;
private WebRtcCallViewModel viewModel;
private boolean enableVideoIfAvailable;
private boolean hasWarnedAboutBluetooth;
private androidx.window.WindowManager windowManager;
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
private ThrottledDebouncer requestNewSizesThrottle;
@ -686,6 +689,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
enableVideoIfAvailable = false;
handleSetMuteVideo(false);
}
if (event.getBluetoothPermissionDenied() && !hasWarnedAboutBluetooth && !isFinishing()) {
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.WebRtcCallActivity__bluetooth_permission_denied)
.setMessage(R.string.WebRtcCallActivity__please_enable_the_nearby_devices_permission_to_use_bluetooth_during_a_call)
.setPositiveButton(R.string.WebRtcCallActivity__open_settings, (d, w) -> startActivity(Permissions.getApplicationSettingsIntent(this)))
.setNegativeButton(R.string.WebRtcCallActivity__not_now, null)
.show();
hasWarnedAboutBluetooth = true;
}
}
private void handleCallPreJoin(@NonNull WebRtcViewModel event) {

View file

@ -96,6 +96,7 @@ class WebRtcViewModel(state: WebRtcServiceState) {
val ringerRecipient: Recipient = state.getCallSetupState(state.callInfoState.activePeer?.callId).ringerRecipient
val activeDevice: SignalAudioManager.AudioDevice = state.localDeviceState.activeDevice
val availableDevices: Set<SignalAudioManager.AudioDevice> = state.localDeviceState.availableDevices
val bluetoothPermissionDenied: Boolean = state.localDeviceState.bluetoothPermissionDenied
val localParticipant: CallParticipant = createLocal(
state.localDeviceState.cameraState,
@ -124,6 +125,7 @@ class WebRtcViewModel(state: WebRtcServiceState) {
participantLimit=$participantLimit,
activeDevice=$activeDevice,
availableDevices=$availableDevices,
bluetoothPermissionDenied=$bluetoothPermissionDenied,
ringGroup=$ringGroup
}
""".trimIndent()

View file

@ -309,6 +309,10 @@ private void processStateless(@NonNull Function1<WebRtcEphemeralState, WebRtcEph
process((s, p) -> p.handleAudioDeviceChanged(s, activeDevice, availableDevices));
}
public void onBluetoothPermissionDenied() {
process((s, p) -> p.handleBluetoothPermissionDenied(s));
}
public void selectAudioDevice(@NonNull SignalAudioManager.AudioDevice desiredDevice) {
process((s, p) -> p.handleSetUserAudioDevice(s, desiredDevice));
}

View file

@ -457,6 +457,13 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
public @NonNull WebRtcServiceState handleBluetoothPermissionDenied(@NonNull WebRtcServiceState currentState) {
return currentState.builder()
.changeLocalDeviceState()
.setBluetoothPermissionDenied(true)
.build();
}
protected @NonNull WebRtcServiceState handleSetUserAudioDevice(@NonNull WebRtcServiceState currentState, @NonNull SignalAudioManager.AudioDevice userDevice) {
Log.i(tag, "handleSetUserAudioDevice not processed");
return currentState;

View file

@ -267,6 +267,11 @@ public final class WebRtcCallService extends Service implements SignalAudioManag
callManager.onAudioDeviceChanged(activeDevice, availableDevices);
}
@Override
public void onBluetoothPermissionDenied() {
callManager.onBluetoothPermissionDenied();
}
private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, @NonNull String phoneNumber) {

View file

@ -14,7 +14,8 @@ data class LocalDeviceState constructor(
var isLandscapeEnabled: Boolean = false,
var deviceOrientation: Orientation = Orientation.PORTRAIT_BOTTOM_EDGE,
var activeDevice: SignalAudioManager.AudioDevice = SignalAudioManager.AudioDevice.NONE,
var availableDevices: Set<SignalAudioManager.AudioDevice> = emptySet()
var availableDevices: Set<SignalAudioManager.AudioDevice> = emptySet(),
var bluetoothPermissionDenied: Boolean = false
) {
fun duplicate(): LocalDeviceState {

View file

@ -125,6 +125,11 @@ public class WebRtcServiceStateBuilder {
toBuild.setAvailableDevices(availableDevices);
return this;
}
public @NonNull LocalDeviceStateBuilder setBluetoothPermissionDenied(boolean bluetoothPermissionDenied) {
toBuild.setBluetoothPermissionDenied(bluetoothPermissionDenied);
return this;
}
}
public class CallSetupStateBuilder {

View file

@ -98,6 +98,7 @@ sealed class SignalAudioManager(protected val context: Context, protected val ev
interface EventListener {
@JvmSuppressWildcards
fun onAudioDeviceChanged(activeDevice: AudioDevice, devices: Set<AudioDevice>)
fun onBluetoothPermissionDenied()
}
}
@ -121,6 +122,7 @@ class FullSignalAudioManager(context: Context, eventListener: EventListener?) :
private var audioDevices: MutableSet<AudioDevice> = mutableSetOf()
private var defaultAudioDevice: AudioDevice = AudioDevice.EARPIECE
private var userSelectedAudioDevice: AudioDevice = AudioDevice.NONE
private var previousBluetoothState: SignalBluetoothManager.State? = null
private var savedAudioMode = AudioManager.MODE_INVALID
private var savedIsSpeakerPhoneOn = false
@ -294,6 +296,11 @@ class FullSignalAudioManager(context: Context, eventListener: EventListener?) :
autoSwitchToBluetooth = false
}
if (previousBluetoothState != null && previousBluetoothState != SignalBluetoothManager.State.PERMISSION_DENIED && signalBluetoothManager.state == SignalBluetoothManager.State.PERMISSION_DENIED) {
eventListener?.onBluetoothPermissionDenied()
}
previousBluetoothState = signalBluetoothManager.state
val newAudioDevice: AudioDevice = when {
audioDevices.contains(userSelectedAudioDevice) -> userSelectedAudioDevice
audioDevices.contains(defaultAudioDevice) -> defaultAudioDevice

View file

@ -162,7 +162,16 @@ class SignalBluetoothManager(
return
}
val devices: List<BluetoothDevice>? = bluetoothHeadset?.connectedDevices
val devices: List<BluetoothDevice>?
try {
devices = bluetoothHeadset?.connectedDevices
} catch (e: SecurityException) {
Log.w(TAG, "Unable to get bluetooth devices", e)
stop()
state = State.PERMISSION_DENIED
return
}
if (devices == null || devices.isEmpty()) {
bluetoothDevice = null
state = State.UNAVAILABLE
@ -320,6 +329,7 @@ class SignalBluetoothManager(
DISCONNECTING,
CONNECTING,
CONNECTED,
PERMISSION_DENIED,
ERROR;
fun shouldUpdate(): Boolean {

View file

@ -1499,6 +1499,14 @@
<string name="WebRtcCallActivity__group_is_too_large_to_ring_the_participants">Group is too large to ring the participants.</string>
<!-- Call status shown when an active call was disconnected (e.g., network hiccup) and is trying to reconnect -->
<string name="WebRtcCallActivity__reconnecting">Reconnecting…</string>
<!-- Title for dialog warning about lacking bluetooth permissions during a call -->
<string name="WebRtcCallActivity__bluetooth_permission_denied">Bluetooth permission denied</string>
<!-- Message for dialog warning about lacking bluetooth permissions during a call and references the permission needed by name -->
<string name="WebRtcCallActivity__please_enable_the_nearby_devices_permission_to_use_bluetooth_during_a_call">Please enable the \"Nearby devices\" permission to use bluetooth during a call.</string>
<!-- Positive action for bluetooth warning dialog to open settings -->
<string name="WebRtcCallActivity__open_settings">Open settings</string>
<!-- Negative aciton for bluetooth warning dialog to dismiss dialog -->
<string name="WebRtcCallActivity__not_now">Not now</string>
<!-- WebRtcCallView -->
<string name="WebRtcCallView__signal_call">Signal Call</string>