2020-04-27 15:51:38 +00:00
|
|
|
package eu.siacs.conversations.xmpp.jingle;
|
|
|
|
|
2020-05-21 13:39:59 +00:00
|
|
|
import android.content.Context;
|
2020-04-27 15:51:38 +00:00
|
|
|
import android.media.AudioManager;
|
|
|
|
import android.media.ToneGenerator;
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
import java.util.concurrent.ScheduledFuture;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
import eu.siacs.conversations.Config;
|
|
|
|
|
|
|
|
import static java.util.Arrays.asList;
|
|
|
|
|
2020-05-03 21:15:21 +00:00
|
|
|
class ToneManager {
|
2020-04-27 15:51:38 +00:00
|
|
|
|
|
|
|
private final ToneGenerator toneGenerator;
|
2020-05-21 13:39:59 +00:00
|
|
|
private final Context context;
|
2020-04-27 15:51:38 +00:00
|
|
|
|
|
|
|
private ToneState state = null;
|
|
|
|
private ScheduledFuture<?> currentTone;
|
2020-05-22 14:25:29 +00:00
|
|
|
private ScheduledFuture<?> currentResetFuture;
|
2020-05-21 13:39:59 +00:00
|
|
|
private boolean appRtcAudioManagerHasControl = false;
|
2020-04-27 15:51:38 +00:00
|
|
|
|
2020-05-21 13:39:59 +00:00
|
|
|
ToneManager(final Context context) {
|
2020-05-10 15:54:13 +00:00
|
|
|
ToneGenerator toneGenerator;
|
|
|
|
try {
|
2020-05-21 13:39:59 +00:00
|
|
|
toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 60);
|
2020-05-10 15:54:13 +00:00
|
|
|
} catch (final RuntimeException e) {
|
|
|
|
Log.e(Config.LOGTAG, "unable to instantiate ToneGenerator", e);
|
|
|
|
toneGenerator = null;
|
|
|
|
}
|
|
|
|
this.toneGenerator = toneGenerator;
|
2020-05-21 13:39:59 +00:00
|
|
|
this.context = context;
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
|
|
|
|
if (isInitiator) {
|
|
|
|
if (asList(RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) {
|
|
|
|
return ToneState.RINGING;
|
|
|
|
}
|
|
|
|
if (state == RtpEndUserState.DECLINED_OR_BUSY) {
|
|
|
|
return ToneState.BUSY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (state == RtpEndUserState.ENDING_CALL) {
|
|
|
|
if (media.contains(Media.VIDEO)) {
|
|
|
|
return ToneState.NULL;
|
|
|
|
} else {
|
|
|
|
return ToneState.ENDING_CALL;
|
|
|
|
}
|
|
|
|
}
|
2020-04-28 05:30:27 +00:00
|
|
|
if (state == RtpEndUserState.CONNECTED) {
|
|
|
|
if (media.contains(Media.VIDEO)) {
|
|
|
|
return ToneState.NULL;
|
|
|
|
} else {
|
|
|
|
return ToneState.CONNECTED;
|
|
|
|
}
|
|
|
|
}
|
2020-04-27 15:51:38 +00:00
|
|
|
return ToneState.NULL;
|
|
|
|
}
|
|
|
|
|
2020-05-21 13:39:59 +00:00
|
|
|
void transition(final RtpEndUserState state, final Set<Media> media) {
|
|
|
|
transition(of(true, state, media), media);
|
|
|
|
}
|
|
|
|
|
|
|
|
void transition(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
|
|
|
|
transition(of(isInitiator, state, media), media);
|
|
|
|
}
|
|
|
|
|
|
|
|
private synchronized void transition(ToneState state, final Set<Media> media) {
|
2020-04-27 15:51:38 +00:00
|
|
|
if (this.state == state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (state == ToneState.NULL && this.state == ToneState.ENDING_CALL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cancelCurrentTone();
|
|
|
|
Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")");
|
2020-05-21 13:39:59 +00:00
|
|
|
if (state != ToneState.NULL) {
|
|
|
|
configureAudioManagerForCall(media);
|
|
|
|
}
|
2020-04-27 15:51:38 +00:00
|
|
|
switch (state) {
|
|
|
|
case RINGING:
|
|
|
|
scheduleWaitingTone();
|
|
|
|
break;
|
2020-04-28 05:30:27 +00:00
|
|
|
case CONNECTED:
|
|
|
|
scheduleConnected();
|
|
|
|
break;
|
2020-04-27 15:51:38 +00:00
|
|
|
case BUSY:
|
|
|
|
scheduleBusy();
|
|
|
|
break;
|
|
|
|
case ENDING_CALL:
|
|
|
|
scheduleEnding();
|
|
|
|
break;
|
2020-05-22 14:25:29 +00:00
|
|
|
case NULL:
|
|
|
|
if (noResetScheduled()) {
|
|
|
|
resetAudioManager();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalStateException("Unable to handle transition to "+state);
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
this.state = state;
|
|
|
|
}
|
|
|
|
|
2020-05-21 13:39:59 +00:00
|
|
|
void setAppRtcAudioManagerHasControl(final boolean appRtcAudioManagerHasControl) {
|
|
|
|
this.appRtcAudioManagerHasControl = appRtcAudioManagerHasControl;
|
|
|
|
}
|
|
|
|
|
2020-04-28 05:30:27 +00:00
|
|
|
private void scheduleConnected() {
|
|
|
|
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
|
2020-05-10 15:54:13 +00:00
|
|
|
startTone(ToneGenerator.TONE_PROP_PROMPT, 200);
|
2020-04-28 05:30:27 +00:00
|
|
|
}, 0, TimeUnit.SECONDS);
|
|
|
|
}
|
|
|
|
|
2020-04-27 15:51:38 +00:00
|
|
|
private void scheduleEnding() {
|
|
|
|
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
|
2020-05-10 15:54:13 +00:00
|
|
|
startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
2020-04-27 15:51:38 +00:00
|
|
|
}, 0, TimeUnit.SECONDS);
|
2020-05-22 14:25:29 +00:00
|
|
|
this.currentResetFuture = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 375, TimeUnit.MILLISECONDS);
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void scheduleBusy() {
|
|
|
|
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
|
2020-05-10 15:54:13 +00:00
|
|
|
startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
|
2020-04-27 15:51:38 +00:00
|
|
|
}, 0, TimeUnit.SECONDS);
|
2020-05-22 14:25:29 +00:00
|
|
|
this.currentResetFuture = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(this::resetAudioManager, 2500, TimeUnit.MILLISECONDS);
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void scheduleWaitingTone() {
|
|
|
|
this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
|
2020-05-10 15:54:13 +00:00
|
|
|
startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750);
|
2020-04-27 15:51:38 +00:00
|
|
|
}, 0, 3, TimeUnit.SECONDS);
|
|
|
|
}
|
|
|
|
|
2020-05-22 14:25:29 +00:00
|
|
|
private boolean noResetScheduled() {
|
|
|
|
return this.currentResetFuture == null || this.currentResetFuture.isDone();
|
|
|
|
}
|
|
|
|
|
2020-04-27 15:51:38 +00:00
|
|
|
private void cancelCurrentTone() {
|
|
|
|
if (currentTone != null) {
|
|
|
|
currentTone.cancel(true);
|
|
|
|
}
|
2020-05-10 15:54:13 +00:00
|
|
|
if (toneGenerator != null) {
|
|
|
|
toneGenerator.stopTone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void startTone(final int toneType, final int durationMs) {
|
|
|
|
if (toneGenerator != null) {
|
|
|
|
this.toneGenerator.startTone(toneType, durationMs);
|
|
|
|
} else {
|
|
|
|
Log.e(Config.LOGTAG, "failed to start tone. ToneGenerator doesn't exist");
|
|
|
|
}
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
|
2020-05-21 13:39:59 +00:00
|
|
|
private void configureAudioManagerForCall(final Set<Media> media) {
|
|
|
|
if (appRtcAudioManagerHasControl) {
|
|
|
|
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": do not configure audio manager because RTC has control");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
if (audioManager == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final boolean isSpeakerPhone = media.contains(Media.VIDEO);
|
|
|
|
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": putting AudioManager into communication mode. speaker=" + isSpeakerPhone);
|
|
|
|
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
|
|
|
|
audioManager.setSpeakerphoneOn(isSpeakerPhone);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void resetAudioManager() {
|
|
|
|
if (appRtcAudioManagerHasControl) {
|
|
|
|
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": do not reset audio manager because RTC has control");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
|
|
if (audioManager == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Log.d(Config.LOGTAG, ToneManager.class.getName() + ": putting AudioManager back into normal mode");
|
|
|
|
audioManager.setMode(AudioManager.MODE_NORMAL);
|
|
|
|
audioManager.setSpeakerphoneOn(false);
|
|
|
|
}
|
|
|
|
|
2020-04-27 15:51:38 +00:00
|
|
|
private enum ToneState {
|
2020-04-28 05:30:27 +00:00
|
|
|
NULL, RINGING, CONNECTED, BUSY, ENDING_CALL
|
2020-04-27 15:51:38 +00:00
|
|
|
}
|
|
|
|
}
|