1package eu.siacs.conversations.xmpp.jingle;
2
3import android.media.AudioManager;
4import android.media.ToneGenerator;
5import android.util.Log;
6
7import java.util.Collections;
8import java.util.Set;
9import java.util.concurrent.ScheduledFuture;
10import java.util.concurrent.TimeUnit;
11
12import eu.siacs.conversations.Config;
13
14import static java.util.Arrays.asList;
15
16public class ToneManager {
17
18 private final ToneGenerator toneGenerator;
19
20 private ToneState state = null;
21 private ScheduledFuture<?> currentTone;
22
23 public ToneManager() {
24 this.toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 35);
25 }
26
27 public void transition(final boolean isInitiator, final RtpEndUserState state) {
28 transition(of(isInitiator, state, Collections.emptySet()));
29 }
30
31 public void transition(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
32 transition(of(isInitiator, state, media));
33 }
34
35 private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
36 if (isInitiator) {
37 if (asList(RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) {
38 return ToneState.RINGING;
39 }
40 if (state == RtpEndUserState.DECLINED_OR_BUSY) {
41 return ToneState.BUSY;
42 }
43 }
44 if (state == RtpEndUserState.ENDING_CALL) {
45 if (media.contains(Media.VIDEO)) {
46 return ToneState.NULL;
47 } else {
48 return ToneState.ENDING_CALL;
49 }
50 }
51 if (state == RtpEndUserState.CONNECTED) {
52 if (media.contains(Media.VIDEO)) {
53 return ToneState.NULL;
54 } else {
55 return ToneState.CONNECTED;
56 }
57 }
58 return ToneState.NULL;
59 }
60
61 private synchronized void transition(ToneState state) {
62 if (this.state == state) {
63 return;
64 }
65 if (state == ToneState.NULL && this.state == ToneState.ENDING_CALL) {
66 return;
67 }
68 cancelCurrentTone();
69 Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")");
70 switch (state) {
71 case RINGING:
72 scheduleWaitingTone();
73 break;
74 case CONNECTED:
75 scheduleConnected();
76 break;
77 case BUSY:
78 scheduleBusy();
79 break;
80 case ENDING_CALL:
81 scheduleEnding();
82 break;
83 }
84 this.state = state;
85 }
86
87 private void scheduleConnected() {
88 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
89 this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_ONE_MIN_BEEP, 145);
90 }, 0, TimeUnit.SECONDS);
91 }
92
93 private void scheduleEnding() {
94 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
95 this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
96 }, 0, TimeUnit.SECONDS);
97 }
98
99 private void scheduleBusy() {
100 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
101 this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
102 }, 0, TimeUnit.SECONDS);
103 }
104
105 private void scheduleWaitingTone() {
106 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
107 this.toneGenerator.startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750);
108 }, 0, 3, TimeUnit.SECONDS);
109 }
110
111 private void cancelCurrentTone() {
112 if (currentTone != null) {
113 currentTone.cancel(true);
114 }
115 toneGenerator.stopTone();
116 }
117
118 private enum ToneState {
119 NULL, RINGING, CONNECTED, BUSY, ENDING_CALL
120 }
121}