ToneManager.java

  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}