1/*
2 * Copyright 2014, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom;
18
19import android.media.AudioManager;
20import android.media.ToneGenerator;
21import android.os.Handler;
22import android.os.Looper;
23
24/**
25 * Play a call-related tone (ringback, busy signal, etc.) through ToneGenerator. To use, create an
26 * instance using InCallTonePlayer.Factory (passing in the TONE_* constant for the tone you want)
27 * and start() it. Implemented on top of {@link Thread} so that the tone plays in its own thread.
28 */
29public final class InCallTonePlayer extends Thread {
30
31    /**
32     * Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
33     */
34    public static class Factory {
35        private final CallAudioManager mCallAudioManager;
36        private final TelecomSystem.SyncRoot mLock;
37
38        Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) {
39            mCallAudioManager = callAudioManager;
40            mLock = lock;
41        }
42
43        InCallTonePlayer createPlayer(int tone) {
44            return new InCallTonePlayer(tone, mCallAudioManager, mLock);
45        }
46    }
47
48    // The possible tones that we can play.
49    public static final int TONE_INVALID = 0;
50    public static final int TONE_BUSY = 1;
51    public static final int TONE_CALL_ENDED = 2;
52    public static final int TONE_OTA_CALL_ENDED = 3;
53    public static final int TONE_CALL_WAITING = 4;
54    public static final int TONE_CDMA_DROP = 5;
55    public static final int TONE_CONGESTION = 6;
56    public static final int TONE_INTERCEPT = 7;
57    public static final int TONE_OUT_OF_SERVICE = 8;
58    public static final int TONE_REDIAL = 9;
59    public static final int TONE_REORDER = 10;
60    public static final int TONE_RING_BACK = 11;
61    public static final int TONE_UNOBTAINABLE_NUMBER = 12;
62    public static final int TONE_VOICE_PRIVACY = 13;
63    public static final int TONE_VIDEO_UPGRADE = 14;
64
65    private static final int RELATIVE_VOLUME_EMERGENCY = 100;
66    private static final int RELATIVE_VOLUME_HIPRI = 80;
67    private static final int RELATIVE_VOLUME_LOPRI = 50;
68
69    // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
70    // value for a tone is exact duration of the tone itself.
71    private static final int TIMEOUT_BUFFER_MILLIS = 20;
72
73    // The tone state.
74    private static final int STATE_OFF = 0;
75    private static final int STATE_ON = 1;
76    private static final int STATE_STOPPED = 2;
77
78    /**
79     * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
80     * when we need focus and when it can be release. This should only be manipulated from the main
81     * thread.
82     */
83    private static int sTonesPlaying = 0;
84
85    private final CallAudioManager mCallAudioManager;
86
87    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
88
89    /** The ID of the tone to play. */
90    private final int mToneId;
91
92    /** Current state of the tone player. */
93    private int mState;
94
95    /** Telecom lock object. */
96    private final TelecomSystem.SyncRoot mLock;
97
98    /**
99     * Initializes the tone player. Private; use the {@link Factory} to create tone players.
100     *
101     * @param toneId ID of the tone to play, see TONE_* constants.
102     */
103    private InCallTonePlayer(
104            int toneId,
105            CallAudioManager callAudioManager,
106            TelecomSystem.SyncRoot lock) {
107        mState = STATE_OFF;
108        mToneId = toneId;
109        mCallAudioManager = callAudioManager;
110        mLock = lock;
111    }
112
113    /** {@inheritDoc} */
114    @Override
115    public void run() {
116        ToneGenerator toneGenerator = null;
117        try {
118            Log.d(this, "run(toneId = %s)", mToneId);
119
120            final int toneType;  // Passed to ToneGenerator.startTone.
121            final int toneVolume;  // Passed to the ToneGenerator constructor.
122            final int toneLengthMillis;
123
124            switch (mToneId) {
125                case TONE_BUSY:
126                    // TODO: CDMA-specific tones
127                    toneType = ToneGenerator.TONE_SUP_BUSY;
128                    toneVolume = RELATIVE_VOLUME_HIPRI;
129                    toneLengthMillis = 4000;
130                    break;
131                case TONE_CALL_ENDED:
132                    toneType = ToneGenerator.TONE_PROP_PROMPT;
133                    toneVolume = RELATIVE_VOLUME_HIPRI;
134                    toneLengthMillis = 200;
135                    break;
136                case TONE_OTA_CALL_ENDED:
137                    // TODO: fill in
138                    throw new IllegalStateException("OTA Call ended NYI.");
139                case TONE_CALL_WAITING:
140                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
141                    toneVolume = RELATIVE_VOLUME_HIPRI;
142                    toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
143                    break;
144                case TONE_CDMA_DROP:
145                    toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
146                    toneVolume = RELATIVE_VOLUME_LOPRI;
147                    toneLengthMillis = 375;
148                    break;
149                case TONE_CONGESTION:
150                    toneType = ToneGenerator.TONE_SUP_CONGESTION;
151                    toneVolume = RELATIVE_VOLUME_HIPRI;
152                    toneLengthMillis = 4000;
153                    break;
154                case TONE_INTERCEPT:
155                    toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
156                    toneVolume = RELATIVE_VOLUME_LOPRI;
157                    toneLengthMillis = 500;
158                    break;
159                case TONE_OUT_OF_SERVICE:
160                    toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
161                    toneVolume = RELATIVE_VOLUME_LOPRI;
162                    toneLengthMillis = 375;
163                    break;
164                case TONE_REDIAL:
165                    toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
166                    toneVolume = RELATIVE_VOLUME_LOPRI;
167                    toneLengthMillis = 5000;
168                    break;
169                case TONE_REORDER:
170                    toneType = ToneGenerator.TONE_CDMA_REORDER;
171                    toneVolume = RELATIVE_VOLUME_HIPRI;
172                    toneLengthMillis = 4000;
173                    break;
174                case TONE_RING_BACK:
175                    toneType = ToneGenerator.TONE_SUP_RINGTONE;
176                    toneVolume = RELATIVE_VOLUME_HIPRI;
177                    toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
178                    break;
179                case TONE_UNOBTAINABLE_NUMBER:
180                    toneType = ToneGenerator.TONE_SUP_ERROR;
181                    toneVolume = RELATIVE_VOLUME_HIPRI;
182                    toneLengthMillis = 4000;
183                    break;
184                case TONE_VOICE_PRIVACY:
185                    // TODO: fill in.
186                    throw new IllegalStateException("Voice privacy tone NYI.");
187                case TONE_VIDEO_UPGRADE:
188                    // Similar to the call waiting tone, but does not repeat.
189                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
190                    toneVolume = RELATIVE_VOLUME_HIPRI;
191                    toneLengthMillis = 4000;
192                    break;
193                default:
194                    throw new IllegalStateException("Bad toneId: " + mToneId);
195            }
196
197            int stream = AudioManager.STREAM_VOICE_CALL;
198            if (mCallAudioManager.isBluetoothAudioOn()) {
199                stream = AudioManager.STREAM_BLUETOOTH_SCO;
200            }
201
202            // If the ToneGenerator creation fails, just continue without it. It is a local audio
203            // signal, and is not as important.
204            try {
205                Log.v(this, "Creating generator");
206                toneGenerator = new ToneGenerator(stream, toneVolume);
207            } catch (RuntimeException e) {
208                Log.w(this, "Failed to create ToneGenerator.", e);
209                return;
210            }
211
212            // TODO: Certain CDMA tones need to check the ringer-volume state before
213            // playing. See CallNotifier.InCallTonePlayer.
214
215            // TODO: Some tones play through the end of a call so we need to inform
216            // CallAudioManager that we want focus the same way that Ringer does.
217
218            synchronized (this) {
219                if (mState != STATE_STOPPED) {
220                    mState = STATE_ON;
221                    toneGenerator.startTone(toneType);
222                    try {
223                        Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
224                                toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
225                        wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
226                    } catch (InterruptedException e) {
227                        Log.w(this, "wait interrupted", e);
228                    }
229                }
230            }
231            mState = STATE_OFF;
232        } finally {
233            if (toneGenerator != null) {
234                toneGenerator.release();
235            }
236            cleanUpTonePlayer();
237        }
238    }
239
240    void startTone() {
241        sTonesPlaying++;
242        if (sTonesPlaying == 1) {
243            mCallAudioManager.setIsTonePlaying(true);
244        }
245
246        start();
247    }
248
249    /**
250     * Stops the tone.
251     */
252    void stopTone() {
253        synchronized (this) {
254            if (mState == STATE_ON) {
255                Log.d(this, "Stopping the tone %d.", mToneId);
256                notify();
257            }
258            mState = STATE_STOPPED;
259        }
260    }
261
262    private void cleanUpTonePlayer() {
263        // Release focus on the main thread.
264        mMainThreadHandler.post(new Runnable() {
265            @Override public void run() {
266                synchronized (mLock) {
267                    if (sTonesPlaying == 0) {
268                        Log.wtf(this, "Over-releasing focus for tone player.");
269                    } else if (--sTonesPlaying == 0) {
270                        mCallAudioManager.setIsTonePlaying(false);
271                    }
272                }
273            }
274        });
275    }
276}
277