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