mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
Refactor webrtc audio management
Attempts to: 1) Successfully play ringtone through speaker instead of earpiece when possible. 2) Manage bluetooth headset connectivity as well as possible 3) Eliminate notification sounds while in-call when possible 4) Make sure audio is correctly setup when receiving calls Fixes #6271 Fixes #6248 Fixes #6238 Fixes #6184 Fixes #6169 // FREEBIE
This commit is contained in:
parent
3904c76261
commit
cd28cd172f
BIN
res/drawable-hdpi/ic_bluetooth_white_24dp.png
Normal file
BIN
res/drawable-hdpi/ic_bluetooth_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 307 B |
BIN
res/drawable-mdpi/ic_bluetooth_white_24dp.png
Normal file
BIN
res/drawable-mdpi/ic_bluetooth_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 213 B |
BIN
res/drawable-xhdpi/ic_bluetooth_white_24dp.png
Normal file
BIN
res/drawable-xhdpi/ic_bluetooth_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 344 B |
BIN
res/drawable-xxhdpi/ic_bluetooth_white_24dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_bluetooth_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 502 B |
BIN
res/drawable-xxxhdpi/ic_bluetooth_white_24dp.png
Normal file
BIN
res/drawable-xxxhdpi/ic_bluetooth_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 595 B |
@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:id="@+id/compoundBackgroundItem" android:drawable="@drawable/webrtc_control_background"/>
|
|
||||||
|
|
||||||
<item android:id="@+id/moreIndicatorItem"
|
|
||||||
android:top="5dp"
|
|
||||||
android:left="5dp"
|
|
||||||
android:right="5dp"
|
|
||||||
android:bottom="5dp">
|
|
||||||
<bitmap android:src="@drawable/redphone_ic_more_indicator_holo_dark"
|
|
||||||
android:gravity="bottom|right" />
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item android:id="@+id/bluetoothItem"
|
|
||||||
android:top="5dp"
|
|
||||||
android:left="5dp"
|
|
||||||
android:right="5dp"
|
|
||||||
android:bottom="5dp">
|
|
||||||
<bitmap android:src="@drawable/ic_phone_bluetooth_speaker_white_24dp"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<!-- Handset earpiece is active -->
|
|
||||||
<item android:id="@+id/handsetItem" android:top="5dp"
|
|
||||||
android:left="5dp"
|
|
||||||
android:right="5dp"
|
|
||||||
android:bottom="5dp">
|
|
||||||
<bitmap android:src="@drawable/ic_phone_in_talk_white_24dp"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<!-- Speakerphone icon showing 'speaker on' state -->
|
|
||||||
<item android:id="@+id/speakerphoneOnItem" android:top="5dp"
|
|
||||||
android:left="5dp"
|
|
||||||
android:right="5dp"
|
|
||||||
android:bottom="5dp">
|
|
||||||
<bitmap android:src="@drawable/ic_volume_up_white_24dp"
|
|
||||||
android:gravity="center" />
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<!--<!– Speakerphone icon showing 'speaker off' state –>-->
|
|
||||||
<!--<item android:id="@+id/speakerphoneOffItem">-->
|
|
||||||
<!--<bitmap android:src="@drawable/ic_volume_mute_white_24dp"-->
|
|
||||||
<!--android:gravity="center" />-->
|
|
||||||
<!--</item>-->
|
|
||||||
|
|
||||||
</layer-list>
|
|
9
res/drawable/webrtc_bluetooth_button.xml
Normal file
9
res/drawable/webrtc_bluetooth_button.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/webrtc_control_background"/>
|
||||||
|
<item android:top="5dp"
|
||||||
|
android:left="5dp"
|
||||||
|
android:right="5dp"
|
||||||
|
android:bottom="5dp"
|
||||||
|
android:drawable="@drawable/ic_bluetooth_white_24dp"/>
|
||||||
|
</layer-list>
|
9
res/drawable/webrtc_speaker_button.xml
Normal file
9
res/drawable/webrtc_speaker_button.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/webrtc_control_background"/>
|
||||||
|
<item android:top="5dp"
|
||||||
|
android:left="5dp"
|
||||||
|
android:right="5dp"
|
||||||
|
android:bottom="5dp"
|
||||||
|
android:drawable="@drawable/ic_volume_up_white_24dp"/>
|
||||||
|
</layer-list>
|
@ -7,12 +7,18 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
tools:background="@color/textsecure_primary">
|
tools:background="@color/textsecure_primary">
|
||||||
|
|
||||||
<ToggleButton android:id="@+id/audioButton"
|
<ToggleButton android:id="@+id/speakerButton"
|
||||||
style="@style/WebRtcCallCompoundButton"
|
style="@style/WebRtcCallCompoundButton"
|
||||||
android:background="@drawable/webrtc_audio_button"
|
android:background="@drawable/webrtc_speaker_button"
|
||||||
tools:checked="true"
|
tools:checked="true"
|
||||||
android:layout_marginRight="15dp"/>
|
android:layout_marginRight="15dp"/>
|
||||||
|
|
||||||
|
<ToggleButton android:id="@+id/bluetoothButton"
|
||||||
|
style="@style/WebRtcCallCompoundButton"
|
||||||
|
android:background="@drawable/webrtc_bluetooth_button"
|
||||||
|
tools:checked="true"
|
||||||
|
android:layout_marginRight="15dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<ToggleButton android:id="@+id/muteButton"
|
<ToggleButton android:id="@+id/muteButton"
|
||||||
style="@style/WebRtcCallCompoundButton"
|
style="@style/WebRtcCallCompoundButton"
|
||||||
|
@ -176,7 +176,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/textsecure_primary"
|
android:background="@color/textsecure_primary"
|
||||||
|
|
||||||
android:paddingLeft="24dp"
|
android:paddingLeft="24dp"
|
||||||
android:paddingRight="24dp"
|
android:paddingRight="24dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
|
@ -18,12 +18,9 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@ -38,7 +35,6 @@ import android.view.WindowManager;
|
|||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen;
|
||||||
import org.thoughtcrime.securesms.components.webrtc.WebRtcIncomingCallOverlay;
|
import org.thoughtcrime.securesms.components.webrtc.WebRtcIncomingCallOverlay;
|
||||||
@ -66,8 +62,6 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
|
||||||
|
|
||||||
private WebRtcCallScreen callScreen;
|
private WebRtcCallScreen callScreen;
|
||||||
private BroadcastReceiver bluetoothStateReceiver;
|
|
||||||
private BroadcastReceiver wiredHeadsetStateReceiver;
|
|
||||||
private SignalServiceNetworkAccess networkAccess;
|
private SignalServiceNetworkAccess networkAccess;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -93,9 +87,6 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
|
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
|
||||||
initializeScreenshotSecurity();
|
initializeScreenshotSecurity();
|
||||||
EventBus.getDefault().register(this);
|
EventBus.getDefault().register(this);
|
||||||
|
|
||||||
registerBluetoothReceiver();
|
|
||||||
registerWiredHeadsetReceiver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -116,8 +107,6 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
super.onPause();
|
super.onPause();
|
||||||
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
|
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
|
||||||
EventBus.getDefault().unregister(this);
|
EventBus.getDefault().unregister(this);
|
||||||
unregisterReceiver(bluetoothStateReceiver);
|
|
||||||
unregisterReceiver(wiredHeadsetStateReceiver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -141,7 +130,8 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
|
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
|
||||||
callScreen.setAudioMuteButtonListener(new AudioMuteButtonListener());
|
callScreen.setAudioMuteButtonListener(new AudioMuteButtonListener());
|
||||||
callScreen.setVideoMuteButtonListener(new VideoMuteButtonListener());
|
callScreen.setVideoMuteButtonListener(new VideoMuteButtonListener());
|
||||||
callScreen.setAudioButtonListener(new AudioButtonListener());
|
callScreen.setSpeakerButtonListener(new SpeakerButtonListener());
|
||||||
|
callScreen.setBluetoothButtonListener(new BluetoothButtonListener());
|
||||||
|
|
||||||
networkAccess = new SignalServiceNetworkAccess(this);
|
networkAccess = new SignalServiceNetworkAccess(this);
|
||||||
}
|
}
|
||||||
@ -154,13 +144,6 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetMuteVideo(boolean muted) {
|
private void handleSetMuteVideo(boolean muted) {
|
||||||
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
|
||||||
|
|
||||||
if (!muted && !audioManager.isWiredHeadsetOn() && !audioManager.isBluetoothScoOn()) {
|
|
||||||
AudioUtils.enableSpeakerphoneRouting(WebRtcCallActivity.this);
|
|
||||||
callScreen.notifyAudioRoutingChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_VIDEO);
|
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_VIDEO);
|
||||||
intent.putExtra(WebRtcCallService.EXTRA_MUTE, muted);
|
intent.putExtra(WebRtcCallService.EXTRA_MUTE, muted);
|
||||||
@ -197,12 +180,6 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||||
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
|
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
|
||||||
startService(intent);
|
startService(intent);
|
||||||
|
|
||||||
// WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
|
|
||||||
//
|
|
||||||
// if (event != null) {
|
|
||||||
// WebRtcCallActivity.this.handleTerminate(event.getRecipient());
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIncomingCall(@NonNull WebRtcViewModel event) {
|
private void handleIncomingCall(@NonNull WebRtcViewModel event) {
|
||||||
@ -326,6 +303,8 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
|
|
||||||
callScreen.setLocalVideoEnabled(event.isLocalVideoEnabled());
|
callScreen.setLocalVideoEnabled(event.isLocalVideoEnabled());
|
||||||
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
|
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
|
||||||
|
callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled());
|
||||||
|
callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {
|
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {
|
||||||
@ -348,56 +327,30 @@ public class WebRtcCallActivity extends Activity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBluetoothReceiver() {
|
private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener {
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(AudioUtils.getScoUpdateAction());
|
|
||||||
bluetoothStateReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onSpeakerChange(boolean isSpeaker) {
|
||||||
callScreen.notifyBluetoothChange();
|
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
|
||||||
}
|
audioManager.setSpeakerphoneOn(isSpeaker);
|
||||||
};
|
|
||||||
|
|
||||||
registerReceiver(bluetoothStateReceiver, filter);
|
if (isSpeaker && audioManager.isBluetoothScoOn()) {
|
||||||
callScreen.notifyBluetoothChange();
|
audioManager.stopBluetoothSco();
|
||||||
|
audioManager.setBluetoothScoOn(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerWiredHeadsetReceiver() {
|
private class BluetoothButtonListener implements WebRtcCallControls.BluetoothButtonListener {
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(AudioUtils.getWiredHeadsetUpdateAction());
|
|
||||||
wiredHeadsetStateReceiver = new BroadcastReceiver() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onBluetoothChange(boolean isBluetooth) {
|
||||||
int state = intent.getIntExtra("state", -1);
|
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
|
||||||
|
|
||||||
if (state == 0 && callScreen.isVideoEnabled()) {
|
if (isBluetooth) {
|
||||||
AudioUtils.enableSpeakerphoneRouting(WebRtcCallActivity.this);
|
audioManager.startBluetoothSco();
|
||||||
callScreen.notifyAudioRoutingChange();
|
audioManager.setBluetoothScoOn(true);
|
||||||
} else if (state == 1) {
|
} else {
|
||||||
AudioUtils.enableDefaultRouting(WebRtcCallActivity.this);
|
audioManager.stopBluetoothSco();
|
||||||
callScreen.notifyAudioRoutingChange();
|
audioManager.setBluetoothScoOn(false);
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
registerReceiver(wiredHeadsetStateReceiver, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AudioButtonListener implements WebRtcCallControls.AudioButtonListener {
|
|
||||||
@Override
|
|
||||||
public void onAudioChange(AudioUtils.AudioMode mode) {
|
|
||||||
switch(mode) {
|
|
||||||
case DEFAULT:
|
|
||||||
AudioUtils.enableDefaultRouting(WebRtcCallActivity.this);
|
|
||||||
break;
|
|
||||||
case SPEAKER:
|
|
||||||
AudioUtils.enableSpeakerphoneRouting(WebRtcCallActivity.this);
|
|
||||||
break;
|
|
||||||
case HEADSET:
|
|
||||||
AudioUtils.enableBluetoothRouting(WebRtcCallActivity.this);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Audio mode " + mode + " is not supported.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,11 @@ package org.thoughtcrime.securesms.components.webrtc;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
@ -17,15 +15,16 @@ import android.widget.LinearLayout;
|
|||||||
import com.tomergoldst.tooltips.ToolTip;
|
import com.tomergoldst.tooltips.ToolTip;
|
||||||
import com.tomergoldst.tooltips.ToolTipsManager;
|
import com.tomergoldst.tooltips.ToolTipsManager;
|
||||||
|
|
||||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
public class WebRtcCallControls extends LinearLayout {
|
public class WebRtcCallControls extends LinearLayout {
|
||||||
|
|
||||||
private CompoundButton audioMuteButton;
|
private CompoundButton audioMuteButton;
|
||||||
private CompoundButton videoMuteButton;
|
private CompoundButton videoMuteButton;
|
||||||
private WebRtcInCallAudioButton audioButton;
|
private CompoundButton speakerButton;
|
||||||
|
private CompoundButton bluetoothButton;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@ -53,37 +52,10 @@ public class WebRtcCallControls extends LinearLayout {
|
|||||||
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
inflater.inflate(R.layout.webrtc_call_controls, this, true);
|
inflater.inflate(R.layout.webrtc_call_controls, this, true);
|
||||||
|
|
||||||
this.audioMuteButton = (CompoundButton) findViewById(R.id.muteButton);
|
this.speakerButton = ViewUtil.findById(this, R.id.speakerButton);
|
||||||
|
this.bluetoothButton = ViewUtil.findById(this, R.id.bluetoothButton);
|
||||||
|
this.audioMuteButton = ViewUtil.findById(this, R.id.muteButton);
|
||||||
this.videoMuteButton = ViewUtil.findById(this, R.id.video_mute_button);
|
this.videoMuteButton = ViewUtil.findById(this, R.id.video_mute_button);
|
||||||
this.audioButton = new WebRtcInCallAudioButton((CompoundButton) findViewById(R.id.audioButton));
|
|
||||||
|
|
||||||
updateAudioButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateAudioButton() {
|
|
||||||
audioButton.setAudioMode(AudioUtils.getCurrentAudioMode(getContext()));
|
|
||||||
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(AudioUtils.getScoUpdateAction());
|
|
||||||
handleBluetoothIntent(getContext().registerReceiver(null, filter));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void handleBluetoothIntent(Intent intent) {
|
|
||||||
if (intent == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!intent.getAction().equals(AudioUtils.getScoUpdateAction())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
|
|
||||||
if (state.equals(AudioManager.SCO_AUDIO_STATE_CONNECTED)) {
|
|
||||||
audioButton.setHeadsetAvailable(true);
|
|
||||||
} else if (state.equals(AudioManager.SCO_AUDIO_STATE_DISCONNECTED)) {
|
|
||||||
audioButton.setHeadsetAvailable(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioMuteButtonListener(final MuteButtonListener listener) {
|
public void setAudioMuteButtonListener(final MuteButtonListener listener) {
|
||||||
@ -104,8 +76,45 @@ public class WebRtcCallControls extends LinearLayout {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioButtonListener(final AudioButtonListener listener) {
|
public void setSpeakerButtonListener(final SpeakerButtonListener listener) {
|
||||||
audioButton.setListener(listener);
|
speakerButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
listener.onSpeakerChange(isChecked);
|
||||||
|
updateAudioState(bluetoothButton.getVisibility() == View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBluetoothButtonListener(final BluetoothButtonListener listener) {
|
||||||
|
bluetoothButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
listener.onBluetoothChange(isChecked);
|
||||||
|
updateAudioState(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateAudioState(boolean isBluetoothAvailable) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(getContext());
|
||||||
|
|
||||||
|
if (!isBluetoothAvailable) {
|
||||||
|
bluetoothButton.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
bluetoothButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioManager.isBluetoothScoOn()) {
|
||||||
|
bluetoothButton.setChecked(true);
|
||||||
|
speakerButton.setChecked(false);
|
||||||
|
} else if (audioManager.isSpeakerphoneOn()) {
|
||||||
|
speakerButton.setChecked(true);
|
||||||
|
bluetoothButton.setChecked(false);
|
||||||
|
} else {
|
||||||
|
speakerButton.setChecked(false);
|
||||||
|
bluetoothButton.setChecked(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVideoEnabled() {
|
public boolean isVideoEnabled() {
|
||||||
@ -116,6 +125,39 @@ public class WebRtcCallControls extends LinearLayout {
|
|||||||
videoMuteButton.setChecked(enabled);
|
videoMuteButton.setChecked(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMicrophoneEnabled(boolean enabled) {
|
||||||
|
audioMuteButton.setChecked(!enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setControlsEnabled(boolean enabled) {
|
||||||
|
if (enabled && Build.VERSION.SDK_INT >= 11) {
|
||||||
|
speakerButton.setAlpha(1.0f);
|
||||||
|
bluetoothButton.setAlpha(1.0f);
|
||||||
|
videoMuteButton.setAlpha(1.0f);
|
||||||
|
audioMuteButton.setAlpha(1.0f);
|
||||||
|
|
||||||
|
speakerButton.setEnabled(true);
|
||||||
|
bluetoothButton.setEnabled(true);
|
||||||
|
videoMuteButton.setEnabled(true);
|
||||||
|
audioMuteButton.setEnabled(true);
|
||||||
|
} else if (!enabled && Build.VERSION.SDK_INT >= 11) {
|
||||||
|
speakerButton.setAlpha(0.3f);
|
||||||
|
bluetoothButton.setAlpha(0.3f);
|
||||||
|
videoMuteButton.setAlpha(0.3f);
|
||||||
|
audioMuteButton.setAlpha(0.3f);
|
||||||
|
|
||||||
|
speakerButton.setChecked(false);
|
||||||
|
bluetoothButton.setChecked(false);
|
||||||
|
videoMuteButton.setChecked(false);
|
||||||
|
audioMuteButton.setChecked(false);
|
||||||
|
|
||||||
|
speakerButton.setEnabled(false);
|
||||||
|
bluetoothButton.setEnabled(false);
|
||||||
|
videoMuteButton.setEnabled(false);
|
||||||
|
audioMuteButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void displayVideoTooltip(ViewGroup viewGroup) {
|
public void displayVideoTooltip(ViewGroup viewGroup) {
|
||||||
if (Build.VERSION.SDK_INT > 15) {
|
if (Build.VERSION.SDK_INT > 15) {
|
||||||
final ToolTipsManager toolTipsManager = new ToolTipsManager();
|
final ToolTipsManager toolTipsManager = new ToolTipsManager();
|
||||||
@ -135,17 +177,23 @@ public class WebRtcCallControls extends LinearLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
updateAudioButton();
|
|
||||||
audioMuteButton.setChecked(false);
|
audioMuteButton.setChecked(false);
|
||||||
videoMuteButton.setChecked(false);
|
videoMuteButton.setChecked(false);
|
||||||
|
speakerButton.setChecked(false);
|
||||||
|
bluetoothButton.setChecked(false);
|
||||||
|
bluetoothButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface MuteButtonListener {
|
public static interface MuteButtonListener {
|
||||||
public void onToggle(boolean isMuted);
|
public void onToggle(boolean isMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static interface AudioButtonListener {
|
public static interface SpeakerButtonListener {
|
||||||
public void onAudioChange(AudioUtils.AudioMode mode);
|
public void onSpeakerChange(boolean isSpeaker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface BluetoothButtonListener {
|
||||||
|
public void onBluetoothChange(boolean isBluetooth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,8 +163,12 @@ public class WebRtcCallScreen extends FrameLayout implements Recipient.Recipient
|
|||||||
this.controls.setVideoMuteButtonListener(listener);
|
this.controls.setVideoMuteButtonListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAudioButtonListener(WebRtcCallControls.AudioButtonListener listener) {
|
public void setSpeakerButtonListener(WebRtcCallControls.SpeakerButtonListener listener) {
|
||||||
this.controls.setAudioButtonListener(listener);
|
this.controls.setSpeakerButtonListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBluetoothButtonListener(WebRtcCallControls.BluetoothButtonListener listener) {
|
||||||
|
this.controls.setBluetoothButtonListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHangupButtonListener(final HangupButtonListener listener) {
|
public void setHangupButtonListener(final HangupButtonListener listener) {
|
||||||
@ -184,12 +188,13 @@ public class WebRtcCallScreen extends FrameLayout implements Recipient.Recipient
|
|||||||
this.cancelIdentityButton.setOnClickListener(listener);
|
this.cancelIdentityButton.setOnClickListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyBluetoothChange() {
|
public void updateAudioState(boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
|
||||||
this.controls.updateAudioButton();
|
this.controls.updateAudioState(isBluetoothAvailable);
|
||||||
|
this.controls.setMicrophoneEnabled(isMicrophoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyAudioRoutingChange() {
|
public void setControlsEnabled(boolean enabled) {
|
||||||
this.controls.updateAudioButton();
|
this.controls.setControlsEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocalVideoEnabled(boolean enabled) {
|
public void setLocalVideoEnabled(boolean enabled) {
|
||||||
|
@ -1,189 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.components.webrtc;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.LayerDrawable;
|
|
||||||
import android.support.v7.widget.PopupMenu;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
|
|
||||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
|
||||||
import org.thoughtcrime.securesms.R;
|
|
||||||
|
|
||||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.DEFAULT;
|
|
||||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.HEADSET;
|
|
||||||
import static org.thoughtcrime.redphone.util.AudioUtils.AudioMode.SPEAKER;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages the audio button displayed on the in-call screen
|
|
||||||
*
|
|
||||||
* The behavior of this button depends on the availability of headset audio, and changes from being a regular
|
|
||||||
* toggle button (enabling speakerphone) to bringing up a model dialog that includes speakerphone, bluetooth,
|
|
||||||
* and regular audio options.
|
|
||||||
*
|
|
||||||
* Based on com.android.phone.InCallTouchUI
|
|
||||||
*
|
|
||||||
* @author Stuart O. Anderson
|
|
||||||
*/
|
|
||||||
public class WebRtcInCallAudioButton {
|
|
||||||
|
|
||||||
private static final String TAG = WebRtcInCallAudioButton.class.getName();
|
|
||||||
|
|
||||||
private final CompoundButton mAudioButton;
|
|
||||||
private boolean headsetAvailable;
|
|
||||||
private AudioUtils.AudioMode currentMode;
|
|
||||||
private Context context;
|
|
||||||
private WebRtcCallControls.AudioButtonListener listener;
|
|
||||||
|
|
||||||
public WebRtcInCallAudioButton(CompoundButton audioButton) {
|
|
||||||
mAudioButton = audioButton;
|
|
||||||
|
|
||||||
currentMode = DEFAULT;
|
|
||||||
headsetAvailable = false;
|
|
||||||
|
|
||||||
updateView();
|
|
||||||
setListener(new WebRtcCallControls.AudioButtonListener() {
|
|
||||||
@Override
|
|
||||||
public void onAudioChange(AudioUtils.AudioMode mode) {
|
|
||||||
//No Action By Default.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
context = audioButton.getContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeadsetAvailable(boolean available) {
|
|
||||||
headsetAvailable = available;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioMode(AudioUtils.AudioMode newMode) {
|
|
||||||
currentMode = newMode;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateView() {
|
|
||||||
// The various layers of artwork for this button come from
|
|
||||||
// redphone_btn_compound_audio.xmlaudio.xml. Keep track of which layers we want to be
|
|
||||||
// visible:
|
|
||||||
//
|
|
||||||
// - This selector shows the blue bar below the button icon when
|
|
||||||
// this button is a toggle *and* it's currently "checked".
|
|
||||||
boolean showToggleStateIndication = false;
|
|
||||||
//
|
|
||||||
// - This is visible if the popup menu is enabled:
|
|
||||||
boolean showMoreIndicator = false;
|
|
||||||
//
|
|
||||||
// - Foreground icons for the button. Exactly one of these is enabled:
|
|
||||||
boolean showSpeakerOnIcon = false;
|
|
||||||
// boolean showSpeakerOffIcon = false;
|
|
||||||
boolean showHandsetIcon = false;
|
|
||||||
boolean showHeadsetIcon = false;
|
|
||||||
|
|
||||||
boolean speakerOn = currentMode == AudioUtils.AudioMode.SPEAKER;
|
|
||||||
|
|
||||||
if (headsetAvailable) {
|
|
||||||
mAudioButton.setEnabled(true);
|
|
||||||
|
|
||||||
// The audio button is NOT a toggle in this state. (And its
|
|
||||||
// setChecked() state is irrelevant since we completely hide the
|
|
||||||
// redphone_btn_compound_background layer anyway.)
|
|
||||||
|
|
||||||
// Update desired layers:
|
|
||||||
showMoreIndicator = true;
|
|
||||||
Log.d(TAG, "UI Mode: " + currentMode);
|
|
||||||
if (currentMode == AudioUtils.AudioMode.HEADSET) {
|
|
||||||
showHeadsetIcon = true;
|
|
||||||
} else if (speakerOn) {
|
|
||||||
showSpeakerOnIcon = true;
|
|
||||||
} else {
|
|
||||||
showHandsetIcon = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mAudioButton.setEnabled(true);
|
|
||||||
|
|
||||||
mAudioButton.setChecked(speakerOn);
|
|
||||||
showSpeakerOnIcon = true;
|
|
||||||
// showSpeakerOnIcon = speakerOn;
|
|
||||||
// showSpeakerOffIcon = !speakerOn;
|
|
||||||
|
|
||||||
showToggleStateIndication = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int HIDDEN = 0;
|
|
||||||
final int VISIBLE = 255;
|
|
||||||
|
|
||||||
LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
|
|
||||||
|
|
||||||
layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
|
|
||||||
.setAlpha(showToggleStateIndication ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
layers.findDrawableByLayerId(R.id.moreIndicatorItem)
|
|
||||||
.setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
layers.findDrawableByLayerId(R.id.bluetoothItem)
|
|
||||||
.setAlpha(showHeadsetIcon ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
layers.findDrawableByLayerId(R.id.handsetItem)
|
|
||||||
.setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
layers.findDrawableByLayerId(R.id.speakerphoneOnItem)
|
|
||||||
.setAlpha(showSpeakerOnIcon ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
// layers.findDrawableByLayerId(R.id.speakerphoneOffItem)
|
|
||||||
// .setAlpha(showSpeakerOffIcon ? VISIBLE : HIDDEN);
|
|
||||||
|
|
||||||
mAudioButton.invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void log(String msg) {
|
|
||||||
Log.d(TAG, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setListener(final WebRtcCallControls.AudioButtonListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
mAudioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
|
|
||||||
if(headsetAvailable) {
|
|
||||||
displayAudioChoiceDialog();
|
|
||||||
} else {
|
|
||||||
currentMode = b ? AudioUtils.AudioMode.SPEAKER : DEFAULT;
|
|
||||||
listener.onAudioChange(currentMode);
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayAudioChoiceDialog() {
|
|
||||||
Log.w(TAG, "Displaying popup...");
|
|
||||||
PopupMenu popupMenu = new PopupMenu(context, mAudioButton);
|
|
||||||
popupMenu.getMenuInflater().inflate(R.menu.redphone_audio_popup_menu, popupMenu.getMenu());
|
|
||||||
popupMenu.setOnMenuItemClickListener(new AudioRoutingPopupListener());
|
|
||||||
popupMenu.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AudioRoutingPopupListener implements PopupMenu.OnMenuItemClickListener {
|
|
||||||
@Override
|
|
||||||
public boolean onMenuItemClick(MenuItem item) {
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.handset:
|
|
||||||
currentMode = DEFAULT;
|
|
||||||
break;
|
|
||||||
case R.id.headset:
|
|
||||||
currentMode = HEADSET;
|
|
||||||
break;
|
|
||||||
case R.id.speaker:
|
|
||||||
currentMode = SPEAKER;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Log.w(TAG, "Unknown item selected in audio popup menu: " + item.toString());
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Selected: " + currentMode + " -- " + item.getItemId());
|
|
||||||
|
|
||||||
listener.onAudioChange(currentMode);
|
|
||||||
updateView();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,19 +32,30 @@ public class WebRtcViewModel {
|
|||||||
private final boolean remoteVideoEnabled;
|
private final boolean remoteVideoEnabled;
|
||||||
private final boolean localVideoEnabled;
|
private final boolean localVideoEnabled;
|
||||||
|
|
||||||
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient, boolean localVideoEnabled, boolean remoteVideoEnabled) {
|
private final boolean isBluetoothAvailable;
|
||||||
this(state, recipient, null, localVideoEnabled, remoteVideoEnabled);
|
private final boolean isMicrophoneEnabled;
|
||||||
|
|
||||||
|
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient,
|
||||||
|
boolean localVideoEnabled, boolean remoteVideoEnabled,
|
||||||
|
boolean isBluetoothAvailable, boolean isMicrophoneEnabled)
|
||||||
|
{
|
||||||
|
this(state, recipient, null,
|
||||||
|
localVideoEnabled, remoteVideoEnabled,
|
||||||
|
isBluetoothAvailable, isMicrophoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient,
|
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient,
|
||||||
@Nullable IdentityKey identityKey,
|
@Nullable IdentityKey identityKey,
|
||||||
boolean localVideoEnabled, boolean remoteVideoEnabled)
|
boolean localVideoEnabled, boolean remoteVideoEnabled,
|
||||||
|
boolean isBluetoothAvailable, boolean isMicrophoneEnabled)
|
||||||
{
|
{
|
||||||
this.state = state;
|
this.state = state;
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
this.identityKey = identityKey;
|
this.identityKey = identityKey;
|
||||||
this.localVideoEnabled = localVideoEnabled;
|
this.localVideoEnabled = localVideoEnabled;
|
||||||
this.remoteVideoEnabled = remoteVideoEnabled;
|
this.remoteVideoEnabled = remoteVideoEnabled;
|
||||||
|
this.isBluetoothAvailable = isBluetoothAvailable;
|
||||||
|
this.isMicrophoneEnabled = isMicrophoneEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull State getState() {
|
public @NonNull State getState() {
|
||||||
@ -68,6 +79,14 @@ public class WebRtcViewModel {
|
|||||||
return localVideoEnabled;
|
return localVideoEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBluetoothAvailable() {
|
||||||
|
return isBluetoothAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMicrophoneEnabled() {
|
||||||
|
return isMicrophoneEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[State: " + state + ", recipient: " + recipient.getNumber() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localVideoEnabled + "]";
|
return "[State: " + state + ", recipient: " + recipient.getNumber() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localVideoEnabled + "]";
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.service;
|
|||||||
|
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
@ -23,10 +25,8 @@ import com.google.protobuf.InvalidProtocolBufferException;
|
|||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.thoughtcrime.redphone.RedPhoneService;
|
import org.thoughtcrime.redphone.RedPhoneService;
|
||||||
import org.thoughtcrime.redphone.audio.IncomingRinger;
|
|
||||||
import org.thoughtcrime.redphone.call.LockManager;
|
import org.thoughtcrime.redphone.call.LockManager;
|
||||||
import org.thoughtcrime.redphone.pstn.IncomingPstnCallReceiver;
|
import org.thoughtcrime.redphone.pstn.IncomingPstnCallReceiver;
|
||||||
import org.thoughtcrime.redphone.util.AudioUtils;
|
|
||||||
import org.thoughtcrime.redphone.util.UncaughtExceptionHandlerManager;
|
import org.thoughtcrime.redphone.util.UncaughtExceptionHandlerManager;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||||
@ -52,7 +52,9 @@ import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos;
|
|||||||
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Connected;
|
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Connected;
|
||||||
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Data;
|
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Data;
|
||||||
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Hangup;
|
import org.thoughtcrime.securesms.webrtc.WebRtcDataProtos.Hangup;
|
||||||
|
import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager;
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
|
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
|
||||||
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||||
import org.webrtc.AudioTrack;
|
import org.webrtc.AudioTrack;
|
||||||
import org.webrtc.DataChannel;
|
import org.webrtc.DataChannel;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
@ -97,12 +99,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
|
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
|
||||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
||||||
|
|
||||||
public class WebRtcCallService extends Service implements InjectableType, PeerConnection.Observer, DataChannel.Observer {
|
public class WebRtcCallService extends Service implements InjectableType, PeerConnection.Observer, DataChannel.Observer, BluetoothStateManager.BluetoothStateListener {
|
||||||
|
|
||||||
private static final String TAG = WebRtcCallService.class.getSimpleName();
|
private static final String TAG = WebRtcCallService.class.getSimpleName();
|
||||||
|
|
||||||
@ -114,6 +115,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
public static final String EXTRA_REMOTE_NUMBER = "remote_number";
|
public static final String EXTRA_REMOTE_NUMBER = "remote_number";
|
||||||
public static final String EXTRA_MUTE = "mute_value";
|
public static final String EXTRA_MUTE = "mute_value";
|
||||||
|
public static final String EXTRA_AVAILABLE = "enabled_value";
|
||||||
public static final String EXTRA_REMOTE_DESCRIPTION = "remote_description";
|
public static final String EXTRA_REMOTE_DESCRIPTION = "remote_description";
|
||||||
public static final String EXTRA_TIMESTAMP = "timestamp";
|
public static final String EXTRA_TIMESTAMP = "timestamp";
|
||||||
public static final String EXTRA_CALL_ID = "call_id";
|
public static final String EXTRA_CALL_ID = "call_id";
|
||||||
@ -129,6 +131,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP";
|
public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP";
|
||||||
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
|
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
|
||||||
public static final String ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO";
|
public static final String ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO";
|
||||||
|
public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE";
|
||||||
|
public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE";
|
||||||
public static final String ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT";
|
public static final String ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT";
|
||||||
public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL";
|
public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL";
|
||||||
|
|
||||||
@ -142,9 +146,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
public static final String ACTION_ICE_CONNECTED = "ICE_CONNECTED";
|
public static final String ACTION_ICE_CONNECTED = "ICE_CONNECTED";
|
||||||
|
|
||||||
private CallState callState = CallState.STATE_IDLE;
|
private CallState callState = CallState.STATE_IDLE;
|
||||||
private boolean audioEnabled = true;
|
private boolean microphoneEnabled = true;
|
||||||
private boolean localVideoEnabled = false;
|
private boolean localVideoEnabled = false;
|
||||||
private boolean remoteVideoEnabled = false;
|
private boolean remoteVideoEnabled = false;
|
||||||
|
private boolean bluetoothAvailable = false;
|
||||||
private Handler serviceHandler = new Handler();
|
private Handler serviceHandler = new Handler();
|
||||||
|
|
||||||
@Inject public SignalMessageSenderFactory messageSenderFactory;
|
@Inject public SignalMessageSenderFactory messageSenderFactory;
|
||||||
@ -152,8 +157,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
private SignalServiceMessageSender messageSender;
|
private SignalServiceMessageSender messageSender;
|
||||||
private PeerConnectionFactory peerConnectionFactory;
|
private PeerConnectionFactory peerConnectionFactory;
|
||||||
private IncomingRinger incomingRinger;
|
private SignalAudioManager audioManager;
|
||||||
private OutgoingRinger outgoingRinger;
|
private BluetoothStateManager bluetoothStateManager;
|
||||||
|
private WiredHeadsetStateReceiver wiredHeadsetStateReceiver;
|
||||||
private LockManager lockManager;
|
private LockManager lockManager;
|
||||||
|
|
||||||
private IncomingPstnCallReceiver callReceiver;
|
private IncomingPstnCallReceiver callReceiver;
|
||||||
@ -178,10 +184,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
initializeResources();
|
initializeResources();
|
||||||
initializeRingers();
|
|
||||||
|
|
||||||
registerIncomingPstnCallReceiver();
|
registerIncomingPstnCallReceiver();
|
||||||
registerUncaughtExceptionHandler();
|
registerUncaughtExceptionHandler();
|
||||||
|
registerWiredHeadsetStateReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -202,6 +208,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
else if (intent.getAction().equals(ACTION_REMOTE_HANGUP)) handleRemoteHangup(intent);
|
else if (intent.getAction().equals(ACTION_REMOTE_HANGUP)) handleRemoteHangup(intent);
|
||||||
else if (intent.getAction().equals(ACTION_SET_MUTE_AUDIO)) handleSetMuteAudio(intent);
|
else if (intent.getAction().equals(ACTION_SET_MUTE_AUDIO)) handleSetMuteAudio(intent);
|
||||||
else if (intent.getAction().equals(ACTION_SET_MUTE_VIDEO)) handleSetMuteVideo(intent);
|
else if (intent.getAction().equals(ACTION_SET_MUTE_VIDEO)) handleSetMuteVideo(intent);
|
||||||
|
else if (intent.getAction().equals(ACTION_BLUETOOTH_CHANGE)) handleBluetoothChange(intent);
|
||||||
|
else if (intent.getAction().equals(ACTION_WIRED_HEADSET_CHANGE)) handleWiredHeadsetChange(intent);
|
||||||
else if (intent.getAction().equals(ACTION_REMOTE_VIDEO_MUTE)) handleRemoteVideoMute(intent);
|
else if (intent.getAction().equals(ACTION_REMOTE_VIDEO_MUTE)) handleRemoteVideoMute(intent);
|
||||||
else if (intent.getAction().equals(ACTION_RESPONSE_MESSAGE)) handleResponseMessage(intent);
|
else if (intent.getAction().equals(ACTION_RESPONSE_MESSAGE)) handleResponseMessage(intent);
|
||||||
else if (intent.getAction().equals(ACTION_ICE_MESSAGE)) handleRemoteIceCandidate(intent);
|
else if (intent.getAction().equals(ACTION_ICE_MESSAGE)) handleRemoteIceCandidate(intent);
|
||||||
@ -227,21 +235,37 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
if (uncaughtExceptionHandlerManager != null) {
|
if (uncaughtExceptionHandlerManager != null) {
|
||||||
uncaughtExceptionHandlerManager.unregister();
|
uncaughtExceptionHandlerManager.unregister();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bluetoothStateManager != null) {
|
||||||
|
bluetoothStateManager.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wiredHeadsetStateReceiver != null) {
|
||||||
|
unregisterReceiver(wiredHeadsetStateReceiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBluetoothStateChanged(boolean isAvailable) {
|
||||||
|
Log.w(TAG, "onBluetoothStateChanged: " + isAvailable);
|
||||||
|
|
||||||
|
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||||
|
intent.setAction(ACTION_BLUETOOTH_CHANGE);
|
||||||
|
intent.putExtra(EXTRA_AVAILABLE, isAvailable);
|
||||||
|
|
||||||
|
startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializers
|
// Initializers
|
||||||
|
|
||||||
private void initializeRingers() {
|
|
||||||
this.outgoingRinger = new OutgoingRinger(this);
|
|
||||||
this.incomingRinger = new IncomingRinger(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
ApplicationContext.getInstance(this).injectDependencies(this);
|
ApplicationContext.getInstance(this).injectDependencies(this);
|
||||||
|
|
||||||
this.callState = CallState.STATE_IDLE;
|
this.callState = CallState.STATE_IDLE;
|
||||||
this.lockManager = new LockManager(this);
|
this.lockManager = new LockManager(this);
|
||||||
this.peerConnectionFactory = new PeerConnectionFactory(new PeerConnectionFactoryOptions());
|
this.peerConnectionFactory = new PeerConnectionFactory(new PeerConnectionFactoryOptions());
|
||||||
|
this.audioManager = new SignalAudioManager(this);
|
||||||
|
this.bluetoothStateManager = new BluetoothStateManager(this, this);
|
||||||
this.messageSender = messageSenderFactory.create();
|
this.messageSender = messageSenderFactory.create();
|
||||||
this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
|
this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
|
||||||
this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
|
this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10));
|
||||||
@ -257,6 +281,20 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager));
|
uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerWiredHeadsetStateReceiver() {
|
||||||
|
wiredHeadsetStateReceiver = new WiredHeadsetStateReceiver();
|
||||||
|
|
||||||
|
String action;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
action = AudioManager.ACTION_HEADSET_PLUG;
|
||||||
|
} else {
|
||||||
|
action = Intent.ACTION_HEADSET_PLUG;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action));
|
||||||
|
}
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
|
|
||||||
private void handleIncomingCall(final Intent intent) {
|
private void handleIncomingCall(final Intent intent) {
|
||||||
@ -278,6 +316,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
timeoutExecutor.schedule(new TimeoutRunnable(this.callId), 2, TimeUnit.MINUTES);
|
timeoutExecutor.schedule(new TimeoutRunnable(this.callId), 2, TimeUnit.MINUTES);
|
||||||
|
|
||||||
initializeVideo();
|
initializeVideo();
|
||||||
|
|
||||||
retrieveTurnServers().addListener(new SuccessOnlyListener<List<PeerConnection.IceServer>>(this.callState, this.callId) {
|
retrieveTurnServers().addListener(new SuccessOnlyListener<List<PeerConnection.IceServer>>(this.callState, this.callId) {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccessContinue(List<PeerConnection.IceServer> result) {
|
public void onSuccessContinue(List<PeerConnection.IceServer> result) {
|
||||||
@ -323,9 +362,11 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
initializeVideo();
|
initializeVideo();
|
||||||
|
|
||||||
sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
||||||
outgoingRinger.playSonar();
|
audioManager.initializeAudioForCall();
|
||||||
|
audioManager.startOutgoingRinger(OutgoingRinger.Type.SONAR);
|
||||||
|
bluetoothStateManager.setWantsConnection(true);
|
||||||
|
|
||||||
setCallInProgressNotification(TYPE_OUTGOING_RINGING, recipient);
|
setCallInProgressNotification(TYPE_OUTGOING_RINGING, recipient);
|
||||||
DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(recipient.getNumber());
|
DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(recipient.getNumber());
|
||||||
@ -355,11 +396,11 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
Log.w(TAG, error);
|
Log.w(TAG, error);
|
||||||
|
|
||||||
if (error instanceof UntrustedIdentityException) {
|
if (error instanceof UntrustedIdentityException) {
|
||||||
sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
} else if (error instanceof UnregisteredUserException) {
|
} else if (error instanceof UnregisteredUserException) {
|
||||||
sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
} else if (error instanceof IOException) {
|
} else if (error instanceof IOException) {
|
||||||
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
terminate();
|
terminate();
|
||||||
@ -396,7 +437,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
@Override
|
@Override
|
||||||
public void onFailureContinue(Throwable error) {
|
public void onFailureContinue(Throwable error) {
|
||||||
Log.w(TAG, error);
|
Log.w(TAG, error);
|
||||||
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
|
||||||
terminate();
|
terminate();
|
||||||
}
|
}
|
||||||
@ -445,7 +486,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
@Override
|
@Override
|
||||||
public void onFailureContinue(Throwable error) {
|
public void onFailureContinue(Throwable error) {
|
||||||
Log.w(TAG, error);
|
Log.w(TAG, error);
|
||||||
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
|
||||||
terminate();
|
terminate();
|
||||||
}
|
}
|
||||||
@ -459,17 +500,19 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
this.callState = CallState.STATE_LOCAL_RINGING;
|
this.callState = CallState.STATE_LOCAL_RINGING;
|
||||||
this.lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
this.lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||||
|
|
||||||
sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
startCallCardActivity();
|
startCallCardActivity();
|
||||||
incomingRinger.start();
|
audioManager.initializeAudioForCall();
|
||||||
|
audioManager.startIncomingRinger();
|
||||||
|
|
||||||
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient);
|
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient);
|
||||||
} else if (callState == CallState.STATE_DIALING) {
|
} else if (callState == CallState.STATE_DIALING) {
|
||||||
if (this.recipient == null) throw new AssertionError("assert");
|
if (this.recipient == null) throw new AssertionError("assert");
|
||||||
|
|
||||||
this.callState = CallState.STATE_REMOTE_RINGING;
|
this.callState = CallState.STATE_REMOTE_RINGING;
|
||||||
this.outgoingRinger.playRing();
|
this.audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING);
|
||||||
|
|
||||||
sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,19 +531,19 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
throw new AssertionError("assert");
|
throw new AssertionError("assert");
|
||||||
}
|
}
|
||||||
|
|
||||||
callState = CallState.STATE_CONNECTED;
|
audioManager.startCommunication(callState == CallState.STATE_REMOTE_RINGING);
|
||||||
|
bluetoothStateManager.setWantsConnection(true);
|
||||||
|
|
||||||
initializeAudio();
|
callState = CallState.STATE_CONNECTED;
|
||||||
outgoingRinger.playComplete();
|
|
||||||
|
|
||||||
if (localVideoEnabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
if (localVideoEnabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
||||||
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
||||||
|
|
||||||
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
|
||||||
setCallInProgressNotification(TYPE_ESTABLISHED, recipient);
|
setCallInProgressNotification(TYPE_ESTABLISHED, recipient);
|
||||||
|
|
||||||
this.peerConnection.setAudioEnabled(audioEnabled);
|
this.peerConnection.setAudioEnabled(microphoneEnabled);
|
||||||
this.peerConnection.setVideoEnabled(localVideoEnabled);
|
this.peerConnection.setVideoEnabled(localVideoEnabled);
|
||||||
|
|
||||||
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder()
|
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder()
|
||||||
@ -529,9 +572,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
|
||||||
outgoingRinger.playBusy();
|
audioManager.startOutgoingRinger(OutgoingRinger.Type.BUSY);
|
||||||
serviceHandler.postDelayed(new Runnable() {
|
serviceHandler.postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -546,7 +589,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
this.callState != CallState.STATE_CONNECTED)
|
this.callState != CallState.STATE_CONNECTED)
|
||||||
{
|
{
|
||||||
Log.w(TAG, "Timing out call: " + this.callId);
|
Log.w(TAG, "Timing out call: " + this.callId);
|
||||||
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
terminate();
|
terminate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -575,8 +618,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
throw new AssertionError("assert");
|
throw new AssertionError("assert");
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingRinger.stop();
|
|
||||||
|
|
||||||
DatabaseFactory.getSmsDatabase(this).insertReceivedCall(recipient.getNumber());
|
DatabaseFactory.getSmsDatabase(this).insertReceivedCall(recipient.getNumber());
|
||||||
|
|
||||||
this.peerConnection.setAudioEnabled(true);
|
this.peerConnection.setAudioEnabled(true);
|
||||||
@ -613,7 +654,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setHangup(Hangup.newBuilder().setId(this.callId)).build().toByteArray()), false));
|
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setHangup(Hangup.newBuilder().setId(this.callId)).build().toByteArray()), false));
|
||||||
sendMessage(this.recipient, SignalServiceCallMessage.forHangup(new HangupMessage(this.callId)));
|
sendMessage(this.recipient, SignalServiceCallMessage.forHangup(new HangupMessage(this.callId)));
|
||||||
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
terminate();
|
terminate();
|
||||||
@ -630,9 +671,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.callState == CallState.STATE_DIALING || this.callState == CallState.STATE_REMOTE_RINGING) {
|
if (this.callState == CallState.STATE_DIALING || this.callState == CallState.STATE_REMOTE_RINGING) {
|
||||||
sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
} else {
|
} else {
|
||||||
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) {
|
if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) {
|
||||||
@ -644,15 +685,17 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
private void handleSetMuteAudio(Intent intent) {
|
private void handleSetMuteAudio(Intent intent) {
|
||||||
boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false);
|
boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false);
|
||||||
this.audioEnabled = !muted;
|
this.microphoneEnabled = !muted;
|
||||||
|
|
||||||
if (this.peerConnection != null) {
|
if (this.peerConnection != null) {
|
||||||
this.peerConnection.setAudioEnabled(this.audioEnabled);
|
this.peerConnection.setAudioEnabled(this.microphoneEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSetMuteVideo(Intent intent) {
|
private void handleSetMuteVideo(Intent intent) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
||||||
boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false);
|
boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false);
|
||||||
|
|
||||||
this.localVideoEnabled = !muted;
|
this.localVideoEnabled = !muted;
|
||||||
|
|
||||||
if (this.peerConnection != null) {
|
if (this.peerConnection != null) {
|
||||||
@ -672,7 +715,42 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(viewModelStateFor(callState), this.recipient, localVideoEnabled, remoteVideoEnabled);
|
if (localVideoEnabled && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn()) {
|
||||||
|
audioManager.setSpeakerphoneOn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(viewModelStateFor(callState), this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBluetoothChange(Intent intent) {
|
||||||
|
this.bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false);
|
||||||
|
|
||||||
|
if (recipient != null) {
|
||||||
|
sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleWiredHeadsetChange(Intent intent) {
|
||||||
|
Log.w(TAG, "handleWiredHeadsetChange...");
|
||||||
|
|
||||||
|
if (callState == CallState.STATE_CONNECTED ||
|
||||||
|
callState == CallState.STATE_DIALING ||
|
||||||
|
callState == CallState.STATE_REMOTE_RINGING)
|
||||||
|
{
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
||||||
|
boolean present = intent.getBooleanExtra(EXTRA_AVAILABLE, false);
|
||||||
|
|
||||||
|
if (present && audioManager.isSpeakerphoneOn()) {
|
||||||
|
audioManager.setSpeakerphoneOn(false);
|
||||||
|
audioManager.setBluetoothScoOn(false);
|
||||||
|
} else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localVideoEnabled) {
|
||||||
|
audioManager.setSpeakerphoneOn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient != null) {
|
||||||
|
sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRemoteVideoMute(Intent intent) {
|
private void handleRemoteVideoMute(Intent intent) {
|
||||||
@ -685,7 +763,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.remoteVideoEnabled = !muted;
|
this.remoteVideoEnabled = !muted;
|
||||||
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled);
|
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper Methods
|
/// Helper Methods
|
||||||
@ -706,15 +784,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
return System.currentTimeMillis() - intent.getLongExtra(WebRtcCallService.EXTRA_TIMESTAMP, -1) > TimeUnit.MINUTES.toMillis(2);
|
return System.currentTimeMillis() - intent.getLongExtra(WebRtcCallService.EXTRA_TIMESTAMP, -1) > TimeUnit.MINUTES.toMillis(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAudio() {
|
|
||||||
AudioManager audioManager = ServiceUtil.getAudioManager(this);
|
|
||||||
AudioUtils.resetConfiguration(this);
|
|
||||||
|
|
||||||
Log.d(TAG, "request STREAM_VOICE_CALL transient audio focus");
|
|
||||||
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL,
|
|
||||||
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeVideo() {
|
private void initializeVideo() {
|
||||||
Util.runOnMainSync(new Runnable() {
|
Util.runOnMainSync(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -737,22 +806,12 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient));
|
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdownAudio() {
|
|
||||||
Log.d(TAG, "reset audio mode and abandon focus");
|
|
||||||
AudioUtils.resetConfiguration(this);
|
|
||||||
AudioManager am = ServiceUtil.getAudioManager(this);
|
|
||||||
am.setMode(AudioManager.MODE_NORMAL);
|
|
||||||
am.abandonAudioFocus(null);
|
|
||||||
am.stopBluetoothSco();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void terminate() {
|
private synchronized void terminate() {
|
||||||
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
|
|
||||||
incomingRinger.stop();
|
audioManager.stop(callState == CallState.STATE_DIALING || callState == CallState.STATE_REMOTE_RINGING || callState == CallState.STATE_CONNECTED);
|
||||||
outgoingRinger.stop();
|
bluetoothStateManager.setWantsConnection(false);
|
||||||
outgoingRinger.playDisconnected();
|
|
||||||
|
|
||||||
if (peerConnection != null) {
|
if (peerConnection != null) {
|
||||||
peerConnection.dispose();
|
peerConnection.dispose();
|
||||||
@ -769,12 +828,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
eglBase = null;
|
eglBase = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdownAudio();
|
|
||||||
|
|
||||||
this.callState = CallState.STATE_IDLE;
|
this.callState = CallState.STATE_IDLE;
|
||||||
this.recipient = null;
|
this.recipient = null;
|
||||||
this.callId = null;
|
this.callId = null;
|
||||||
this.audioEnabled = true;
|
this.microphoneEnabled = true;
|
||||||
this.localVideoEnabled = false;
|
this.localVideoEnabled = false;
|
||||||
this.remoteVideoEnabled = false;
|
this.remoteVideoEnabled = false;
|
||||||
this.pendingIceUpdates = null;
|
this.pendingIceUpdates = null;
|
||||||
@ -784,17 +841,19 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
|
|
||||||
private void sendMessage(@NonNull WebRtcViewModel.State state,
|
private void sendMessage(@NonNull WebRtcViewModel.State state,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
boolean localVideoEnabled, boolean remoteVideoEnabled)
|
boolean localVideoEnabled, boolean remoteVideoEnabled,
|
||||||
|
boolean bluetoothAvailable, boolean microphoneEnabled)
|
||||||
{
|
{
|
||||||
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localVideoEnabled, remoteVideoEnabled));
|
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(@NonNull WebRtcViewModel.State state,
|
private void sendMessage(@NonNull WebRtcViewModel.State state,
|
||||||
@NonNull Recipient recipient,
|
@NonNull Recipient recipient,
|
||||||
@NonNull IdentityKey identityKey,
|
@NonNull IdentityKey identityKey,
|
||||||
boolean localVideoEnabled, boolean remoteVideoEnabled)
|
boolean localVideoEnabled, boolean remoteVideoEnabled,
|
||||||
|
boolean bluetoothAvailable, boolean microphoneEnabled)
|
||||||
{
|
{
|
||||||
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localVideoEnabled, remoteVideoEnabled));
|
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFutureTask<Boolean> sendMessage(@NonNull final Recipient recipient,
|
private ListenableFutureTask<Boolean> sendMessage(@NonNull final Recipient recipient,
|
||||||
@ -1022,6 +1081,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
return futureTask;
|
return futureTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
private WebRtcViewModel.State viewModelStateFor(CallState state) {
|
private WebRtcViewModel.State viewModelStateFor(CallState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_CONNECTED: return WebRtcViewModel.State.CALL_CONNECTED;
|
case STATE_CONNECTED: return WebRtcViewModel.State.CALL_CONNECTED;
|
||||||
@ -1035,6 +1096,20 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
|||||||
return WebRtcViewModel.State.CALL_DISCONNECTED;
|
return WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
private static class WiredHeadsetStateReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int state = intent.getIntExtra("state", -1);
|
||||||
|
|
||||||
|
Intent serviceIntent = new Intent(context, WebRtcCallService.class);
|
||||||
|
serviceIntent.setAction(WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE);
|
||||||
|
serviceIntent.putExtra(WebRtcCallService.EXTRA_AVAILABLE, state != 0);
|
||||||
|
context.startService(serviceIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class TimeoutRunnable implements Runnable {
|
private class TimeoutRunnable implements Runnable {
|
||||||
|
|
||||||
private final long callId;
|
private final long callId;
|
||||||
|
@ -0,0 +1,227 @@
|
|||||||
|
package org.thoughtcrime.securesms.webrtc.audio;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothClass;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothHeadset;
|
||||||
|
import android.bluetooth.BluetoothProfile;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BluetoothStateManager {
|
||||||
|
|
||||||
|
private static final String TAG = BluetoothStateManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private enum ScoConnection {
|
||||||
|
DISCONNECTED,
|
||||||
|
IN_PROGRESS,
|
||||||
|
CONNECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Object LOCK = new Object();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final BluetoothAdapter bluetoothAdapter;
|
||||||
|
private final BluetoothScoReceiver bluetoothScoReceiver;
|
||||||
|
private final BluetoothConnectionReceiver bluetoothConnectionReceiver;
|
||||||
|
private final BluetoothStateListener listener;
|
||||||
|
|
||||||
|
private BluetoothHeadset bluetoothHeadset = null;
|
||||||
|
private ScoConnection scoConnection = ScoConnection.DISCONNECTED;
|
||||||
|
private boolean wantsConnection = false;
|
||||||
|
|
||||||
|
public BluetoothStateManager(@NonNull Context context, @Nullable BluetoothStateListener listener) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
this.bluetoothScoReceiver = new BluetoothScoReceiver();
|
||||||
|
this.bluetoothConnectionReceiver = new BluetoothConnectionReceiver();
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
requestHeadsetProxyProfile();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
context.registerReceiver(bluetoothConnectionReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent sticky = context.registerReceiver(bluetoothScoReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
|
||||||
|
|
||||||
|
if (sticky != null) {
|
||||||
|
bluetoothScoReceiver.onReceive(context, sticky);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBluetoothStateChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDestroy() {
|
||||||
|
if (bluetoothHeadset != null && bluetoothAdapter != null && Build.VERSION.SDK_INT >= 11) {
|
||||||
|
this.bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 11 && bluetoothConnectionReceiver != null) {
|
||||||
|
context.unregisterReceiver(bluetoothConnectionReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bluetoothScoReceiver != null) {
|
||||||
|
context.unregisterReceiver(bluetoothScoReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bluetoothHeadset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWantsConnection(boolean enabled) {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
this.wantsConnection = enabled;
|
||||||
|
|
||||||
|
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
|
||||||
|
audioManager.startBluetoothSco();
|
||||||
|
scoConnection = ScoConnection.IN_PROGRESS;
|
||||||
|
} else if (!wantsConnection && scoConnection == ScoConnection.CONNECTED) {
|
||||||
|
audioManager.stopBluetoothSco();
|
||||||
|
audioManager.setBluetoothScoOn(false);
|
||||||
|
scoConnection = ScoConnection.DISCONNECTED;
|
||||||
|
} else if (!wantsConnection && scoConnection == ScoConnection.IN_PROGRESS) {
|
||||||
|
audioManager.stopBluetoothSco();
|
||||||
|
scoConnection = ScoConnection.DISCONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleBluetoothStateChange() {
|
||||||
|
if (listener != null) listener.onBluetoothStateChanged(isBluetoothAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBluetoothAvailable() {
|
||||||
|
try {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) return false;
|
||||||
|
if (!audioManager.isBluetoothScoAvailableOffCall()) return false;
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= 11) {
|
||||||
|
return bluetoothHeadset != null && !bluetoothHeadset.getConnectedDevices().isEmpty();
|
||||||
|
} else {
|
||||||
|
return audioManager.isBluetoothScoOn() || audioManager.isBluetoothA2dpOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getScoChangeIntent() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 14) {
|
||||||
|
return AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED;
|
||||||
|
} else {
|
||||||
|
return AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void requestHeadsetProxyProfile() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 11) {
|
||||||
|
this.bluetoothAdapter.getProfileProxy(context, new BluetoothProfile.ServiceListener() {
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||||
|
if (profile == BluetoothProfile.HEADSET) {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
bluetoothHeadset = (BluetoothHeadset) proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent sticky = context.registerReceiver(null, new IntentFilter(getScoChangeIntent()));
|
||||||
|
bluetoothScoReceiver.onReceive(context, sticky);
|
||||||
|
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (wantsConnection && isBluetoothAvailable() && scoConnection == ScoConnection.DISCONNECTED) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
audioManager.startBluetoothSco();
|
||||||
|
scoConnection = ScoConnection.IN_PROGRESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBluetoothStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(int profile) {
|
||||||
|
Log.w(TAG, "onServiceDisconnected");
|
||||||
|
if (profile == BluetoothProfile.HEADSET) {
|
||||||
|
bluetoothHeadset = null;
|
||||||
|
handleBluetoothStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, BluetoothProfile.HEADSET);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothScoReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (intent == null) return;
|
||||||
|
Log.w(TAG, "onReceive");
|
||||||
|
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (getScoChangeIntent().equals(intent.getAction())) {
|
||||||
|
int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR);
|
||||||
|
|
||||||
|
if (status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 11 && bluetoothHeadset != null) {
|
||||||
|
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
|
||||||
|
|
||||||
|
for (BluetoothDevice device : devices) {
|
||||||
|
if (bluetoothHeadset.isAudioConnected(device)) {
|
||||||
|
int deviceClass = device.getBluetoothClass().getDeviceClass();
|
||||||
|
|
||||||
|
if (deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
|
||||||
|
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
|
||||||
|
deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)
|
||||||
|
{
|
||||||
|
scoConnection = ScoConnection.CONNECTED;
|
||||||
|
|
||||||
|
if (wantsConnection) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
audioManager.setBluetoothScoOn(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBluetoothStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothConnectionReceiver extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.w(TAG, "onReceive");
|
||||||
|
handleBluetoothStateChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BluetoothStateListener {
|
||||||
|
public void onBluetoothStateChanged(boolean isAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
142
src/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java
Normal file
142
src/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package org.thoughtcrime.securesms.webrtc.audio;
|
||||||
|
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.media.RingtoneManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class IncomingRinger {
|
||||||
|
|
||||||
|
private static final String TAG = IncomingRinger.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Vibrator vibrator;
|
||||||
|
|
||||||
|
private MediaPlayer player;
|
||||||
|
|
||||||
|
public IncomingRinger(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(boolean speakerphone) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
if (player != null) player.release();
|
||||||
|
player = createPlayer();
|
||||||
|
|
||||||
|
int ringerMode = audioManager.getRingerMode();
|
||||||
|
|
||||||
|
if (shouldVibrate(context, player, ringerMode)) {
|
||||||
|
Log.i(TAG, "Starting vibration");
|
||||||
|
vibrator.vibrate(VIBRATE_PATTERN, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||||
|
try {
|
||||||
|
if (!player.isPlaying()) {
|
||||||
|
player.prepare();
|
||||||
|
player.start();
|
||||||
|
Log.w(TAG, "Playing ringtone now...");
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Ringtone is already playing, declining to restart.");
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException | IOException e) {
|
||||||
|
Log.w(TAG, e);
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Not ringing, mode: " + ringerMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (speakerphone) {
|
||||||
|
audioManager.setSpeakerphoneOn(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
if (player != null) {
|
||||||
|
Log.w(TAG, "Stopping ringer");
|
||||||
|
player.release();
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Cancelling vibrator");
|
||||||
|
vibrator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldVibrate(Context context, MediaPlayer player, int ringerMode) {
|
||||||
|
if (player == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
return shouldVibrateNew(context, ringerMode);
|
||||||
|
} else {
|
||||||
|
return shouldVibrateOld(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
private boolean shouldVibrateNew(Context context, int ringerMode) {
|
||||||
|
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
||||||
|
|
||||||
|
if (vibrator == null || !vibrator.hasVibrator()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean vibrateWhenRinging = Settings.System.getInt(context.getContentResolver(), "vibrate_when_ringing", 0) != 0;
|
||||||
|
|
||||||
|
if (vibrateWhenRinging) {
|
||||||
|
return ringerMode != AudioManager.RINGER_MODE_SILENT;
|
||||||
|
} else {
|
||||||
|
return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldVibrateOld(Context context) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
return audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaPlayer createPlayer() {
|
||||||
|
try {
|
||||||
|
MediaPlayer mediaPlayer = new MediaPlayer();
|
||||||
|
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
|
||||||
|
|
||||||
|
mediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
|
||||||
|
mediaPlayer.setDataSource(context, ringtoneUri);
|
||||||
|
mediaPlayer.setLooping(true);
|
||||||
|
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
|
||||||
|
|
||||||
|
return mediaPlayer;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to create player for incoming call ringer");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
|
||||||
|
@Override
|
||||||
|
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||||
|
Log.w(TAG, "onError(" + mp + ", " + what + ", " + extra);
|
||||||
|
player = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,84 +4,56 @@ import android.content.Context;
|
|||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
public class OutgoingRinger {
|
||||||
* Handles loading and playing the sequence of sounds we use to indicate call initialization.
|
|
||||||
*
|
|
||||||
* @author Stuart O. Anderson
|
|
||||||
*/
|
|
||||||
public class OutgoingRinger implements MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener {
|
|
||||||
|
|
||||||
private static final String TAG = OutgoingRinger.class.getSimpleName();
|
private static final String TAG = OutgoingRinger.class.getSimpleName();
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
SONAR,
|
||||||
|
RINGING,
|
||||||
|
BUSY
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
private MediaPlayer mediaPlayer;
|
private MediaPlayer mediaPlayer;
|
||||||
private int currentSoundID;
|
|
||||||
private boolean loopEnabled;
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public OutgoingRinger(Context context) {
|
public OutgoingRinger(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
loopEnabled = true;
|
|
||||||
currentSoundID = -1;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playSonar() {
|
public void start(Type type) {
|
||||||
start(R.raw.redphone_sonarping);
|
int soundId;
|
||||||
|
|
||||||
|
if (type == Type.SONAR) soundId = R.raw.redphone_sonarping;
|
||||||
|
else if (type == Type.RINGING) soundId = R.raw.redphone_outring;
|
||||||
|
else if (type == Type.BUSY) soundId = R.raw.redphone_busy;
|
||||||
|
else throw new IllegalArgumentException("Not a valid sound type");
|
||||||
|
|
||||||
|
if( mediaPlayer != null ) {
|
||||||
|
mediaPlayer.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playRing() {
|
|
||||||
start(R.raw.redphone_outring);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playComplete() {
|
|
||||||
stop(R.raw.webrtc_completed);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playDisconnected() {
|
|
||||||
stop(R.raw.webrtc_disconnected);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void playBusy() {
|
|
||||||
start(R.raw.redphone_busy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSound( int soundID ) {
|
|
||||||
currentSoundID = soundID;
|
|
||||||
loopEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start( int soundID ) {
|
|
||||||
if( soundID == currentSoundID ) return;
|
|
||||||
setSound( soundID );
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start() {
|
|
||||||
if( mediaPlayer != null ) mediaPlayer.release();
|
|
||||||
mediaPlayer = new MediaPlayer();
|
mediaPlayer = new MediaPlayer();
|
||||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
|
mediaPlayer.setAudioStreamType(AudioManager.STREAM_VOICE_CALL);
|
||||||
mediaPlayer.setOnCompletionListener(this);
|
mediaPlayer.setLooping(true);
|
||||||
mediaPlayer.setOnPreparedListener(this);
|
|
||||||
mediaPlayer.setLooping(loopEnabled);
|
|
||||||
|
|
||||||
String packageName = context.getPackageName();
|
String packageName = context.getPackageName();
|
||||||
Uri dataUri = Uri.parse("android.resource://" + packageName + "/" + currentSoundID);
|
Uri dataUri = Uri.parse("android.resource://" + packageName + "/" + soundId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mediaPlayer.setDataSource(context, dataUri);
|
mediaPlayer.setDataSource(context, dataUri);
|
||||||
mediaPlayer.prepareAsync();
|
mediaPlayer.prepare();
|
||||||
|
mediaPlayer.start();
|
||||||
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
|
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
// TODO Auto-generated catch block
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,37 +61,5 @@ public class OutgoingRinger implements MediaPlayer.OnCompletionListener, MediaPl
|
|||||||
if (mediaPlayer == null) return;
|
if (mediaPlayer == null) return;
|
||||||
mediaPlayer.release();
|
mediaPlayer.release();
|
||||||
mediaPlayer = null;
|
mediaPlayer = null;
|
||||||
|
|
||||||
currentSoundID = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stop( int soundID ) {
|
|
||||||
setSound( soundID );
|
|
||||||
loopEnabled = false;
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onCompletion(MediaPlayer mp) {
|
|
||||||
//mediaPlayer.release();
|
|
||||||
//mediaPlayer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onPrepared(MediaPlayer mp) {
|
|
||||||
AudioManager am = ServiceUtil.getAudioManager(context);
|
|
||||||
|
|
||||||
if (am.isBluetoothScoAvailableOffCall()) {
|
|
||||||
Log.d(TAG, "bluetooth sco is available");
|
|
||||||
try {
|
|
||||||
am.startBluetoothSco();
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
// Lollipop bug (https://stackoverflow.com/questions/26642218/audiomanager-startbluetoothsco-crashes-on-android-lollipop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mp.start();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
package org.thoughtcrime.securesms.webrtc.audio;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.SoundPool;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
public class SignalAudioManager {
|
||||||
|
|
||||||
|
private static final String TAG = SignalAudioManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final IncomingRinger incomingRinger;
|
||||||
|
private final OutgoingRinger outgoingRinger;
|
||||||
|
|
||||||
|
private final SoundPool soundPool;
|
||||||
|
private final int connectedSoundId;
|
||||||
|
private final int disconnectedSoundId;
|
||||||
|
|
||||||
|
public SignalAudioManager(@NonNull Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.incomingRinger = new IncomingRinger(context);
|
||||||
|
this.outgoingRinger = new OutgoingRinger(context);
|
||||||
|
this.soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0);
|
||||||
|
|
||||||
|
this.connectedSoundId = this.soundPool.load(context, R.raw.webrtc_completed, 1);
|
||||||
|
this.disconnectedSoundId = this.soundPool.load(context, R.raw.webrtc_disconnected, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initializeAudioForCall() {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE);
|
||||||
|
} else {
|
||||||
|
audioManager.requestAudioFocus(null, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startIncomingRinger() {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
boolean speaker = !audioManager.isWiredHeadsetOn() && !audioManager.isBluetoothScoOn();
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||||
|
} else {
|
||||||
|
audioManager.setMode(AudioManager.MODE_IN_CALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
audioManager.setMicrophoneMute(false);
|
||||||
|
audioManager.setSpeakerphoneOn(speaker);
|
||||||
|
|
||||||
|
incomingRinger.start(speaker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startOutgoingRinger(OutgoingRinger.Type type) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
audioManager.setMicrophoneMute(false);
|
||||||
|
|
||||||
|
if (type == OutgoingRinger.Type.SONAR) {
|
||||||
|
audioManager.setSpeakerphoneOn(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
||||||
|
} else {
|
||||||
|
audioManager.setMode(AudioManager.MODE_IN_CALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
outgoingRinger.start(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startCommunication(boolean preserveSpeakerphone) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
incomingRinger.stop();
|
||||||
|
outgoingRinger.stop();
|
||||||
|
|
||||||
|
if (!preserveSpeakerphone) {
|
||||||
|
audioManager.setSpeakerphoneOn(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
soundPool.play(connectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(boolean playDisconnected) {
|
||||||
|
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||||
|
|
||||||
|
incomingRinger.stop();
|
||||||
|
outgoingRinger.stop();
|
||||||
|
|
||||||
|
if (playDisconnected) {
|
||||||
|
soundPool.play(disconnectedSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioManager.isBluetoothScoOn()) {
|
||||||
|
audioManager.setBluetoothScoOn(false);
|
||||||
|
audioManager.stopBluetoothSco();
|
||||||
|
}
|
||||||
|
|
||||||
|
audioManager.setSpeakerphoneOn(false);
|
||||||
|
audioManager.setMicrophoneMute(false);
|
||||||
|
audioManager.setMode(AudioManager.MODE_NORMAL);
|
||||||
|
audioManager.abandonAudioFocus(null);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user