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
16class ToneManager {
17
18 private final ToneGenerator toneGenerator;
19
20 private ToneState state = null;
21 private ScheduledFuture<?> currentTone;
22
23 ToneManager() {
24 ToneGenerator toneGenerator;
25 try {
26 toneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, 35);
27 } catch (final RuntimeException e) {
28 Log.e(Config.LOGTAG, "unable to instantiate ToneGenerator", e);
29 toneGenerator = null;
30 }
31 this.toneGenerator = toneGenerator;
32 }
33
34 void transition(final RtpEndUserState state) {
35 transition(of(true, state, Collections.emptySet()));
36 }
37
38 void transition(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
39 transition(of(isInitiator, state, media));
40 }
41
42 private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set<Media> media) {
43 if (isInitiator) {
44 if (asList(RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) {
45 return ToneState.RINGING;
46 }
47 if (state == RtpEndUserState.DECLINED_OR_BUSY) {
48 return ToneState.BUSY;
49 }
50 }
51 if (state == RtpEndUserState.ENDING_CALL) {
52 if (media.contains(Media.VIDEO)) {
53 return ToneState.NULL;
54 } else {
55 return ToneState.ENDING_CALL;
56 }
57 }
58 if (state == RtpEndUserState.CONNECTED) {
59 if (media.contains(Media.VIDEO)) {
60 return ToneState.NULL;
61 } else {
62 return ToneState.CONNECTED;
63 }
64 }
65 return ToneState.NULL;
66 }
67
68 private synchronized void transition(ToneState state) {
69 if (this.state == state) {
70 return;
71 }
72 if (state == ToneState.NULL && this.state == ToneState.ENDING_CALL) {
73 return;
74 }
75 cancelCurrentTone();
76 Log.d(Config.LOGTAG, getClass().getName() + ".transition(" + state + ")");
77 switch (state) {
78 case RINGING:
79 scheduleWaitingTone();
80 break;
81 case CONNECTED:
82 scheduleConnected();
83 break;
84 case BUSY:
85 scheduleBusy();
86 break;
87 case ENDING_CALL:
88 scheduleEnding();
89 break;
90 }
91 this.state = state;
92 }
93
94 private void scheduleConnected() {
95 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
96 startTone(ToneGenerator.TONE_PROP_PROMPT, 200);
97 }, 0, TimeUnit.SECONDS);
98 }
99
100 private void scheduleEnding() {
101 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
102 startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
103 }, 0, TimeUnit.SECONDS);
104 }
105
106 private void scheduleBusy() {
107 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.schedule(() -> {
108 startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
109 }, 0, TimeUnit.SECONDS);
110 }
111
112 private void scheduleWaitingTone() {
113 this.currentTone = JingleConnectionManager.SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(() -> {
114 startTone(ToneGenerator.TONE_CDMA_DIAL_TONE_LITE, 750);
115 }, 0, 3, TimeUnit.SECONDS);
116 }
117
118 private void cancelCurrentTone() {
119 if (currentTone != null) {
120 currentTone.cancel(true);
121 }
122 if (toneGenerator != null) {
123 toneGenerator.stopTone();
124 }
125 }
126
127 private void startTone(final int toneType, final int durationMs) {
128 if (toneGenerator != null) {
129 this.toneGenerator.startTone(toneType, durationMs);
130 } else {
131 Log.e(Config.LOGTAG, "failed to start tone. ToneGenerator doesn't exist");
132 }
133 }
134
135 private enum ToneState {
136 NULL, RINGING, CONNECTED, BUSY, ENDING_CALL
137 }
138}