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 
17 package com.android.server.telecom;
18 
19 import android.media.AudioManager;
20 import android.media.ToneGenerator;
21 import android.os.Handler;
22 import 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  */
29 public 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 
Factory(CallAudioManager callAudioManager)37         Factory(CallAudioManager callAudioManager) {
38             mCallAudioManager = callAudioManager;
39         }
40 
createPlayer(int tone)41         InCallTonePlayer createPlayer(int tone) {
42             return new InCallTonePlayer(tone, mCallAudioManager);
43         }
44     }
45 
46     // The possible tones that we can play.
47     public static final int TONE_INVALID = 0;
48     public static final int TONE_BUSY = 1;
49     public static final int TONE_CALL_ENDED = 2;
50     public static final int TONE_OTA_CALL_ENDED = 3;
51     public static final int TONE_CALL_WAITING = 4;
52     public static final int TONE_CDMA_DROP = 5;
53     public static final int TONE_CONGESTION = 6;
54     public static final int TONE_INTERCEPT = 7;
55     public static final int TONE_OUT_OF_SERVICE = 8;
56     public static final int TONE_REDIAL = 9;
57     public static final int TONE_REORDER = 10;
58     public static final int TONE_RING_BACK = 11;
59     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
60     public static final int TONE_VOICE_PRIVACY = 13;
61 
62     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
63     private static final int RELATIVE_VOLUME_HIPRI = 80;
64     private static final int RELATIVE_VOLUME_LOPRI = 50;
65 
66     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
67     // value for a tone is exact duration of the tone itself.
68     private static final int TIMEOUT_BUFFER_MILLIS = 20;
69 
70     // The tone state.
71     private static final int STATE_OFF = 0;
72     private static final int STATE_ON = 1;
73     private static final int STATE_STOPPED = 2;
74 
75     /**
76      * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
77      * when we need focus and when it can be release. This should only be manipulated from the main
78      * thread.
79      */
80     private static int sTonesPlaying = 0;
81 
82     private final CallAudioManager mCallAudioManager;
83 
84     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
85 
86     /** The ID of the tone to play. */
87     private final int mToneId;
88 
89     /** Current state of the tone player. */
90     private int mState;
91 
92     /**
93      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
94      *
95      * @param toneId ID of the tone to play, see TONE_* constants.
96      */
InCallTonePlayer(int toneId, CallAudioManager callAudioManager)97     private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) {
98         mState = STATE_OFF;
99         mToneId = toneId;
100         mCallAudioManager = callAudioManager;
101     }
102 
103     /** {@inheritDoc} */
104     @Override
run()105     public void run() {
106         ToneGenerator toneGenerator = null;
107         try {
108             Log.d(this, "run(toneId = %s)", mToneId);
109 
110             final int toneType;  // Passed to ToneGenerator.startTone.
111             final int toneVolume;  // Passed to the ToneGenerator constructor.
112             final int toneLengthMillis;
113 
114             switch (mToneId) {
115                 case TONE_BUSY:
116                     // TODO: CDMA-specific tones
117                     toneType = ToneGenerator.TONE_SUP_BUSY;
118                     toneVolume = RELATIVE_VOLUME_HIPRI;
119                     toneLengthMillis = 4000;
120                     break;
121                 case TONE_CALL_ENDED:
122                     toneType = ToneGenerator.TONE_PROP_PROMPT;
123                     toneVolume = RELATIVE_VOLUME_HIPRI;
124                     toneLengthMillis = 200;
125                     break;
126                 case TONE_OTA_CALL_ENDED:
127                     // TODO: fill in
128                     throw new IllegalStateException("OTA Call ended NYI.");
129                 case TONE_CALL_WAITING:
130                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
131                     toneVolume = RELATIVE_VOLUME_HIPRI;
132                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
133                     break;
134                 case TONE_CDMA_DROP:
135                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
136                     toneVolume = RELATIVE_VOLUME_LOPRI;
137                     toneLengthMillis = 375;
138                     break;
139                 case TONE_CONGESTION:
140                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
141                     toneVolume = RELATIVE_VOLUME_HIPRI;
142                     toneLengthMillis = 4000;
143                     break;
144                 case TONE_INTERCEPT:
145                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
146                     toneVolume = RELATIVE_VOLUME_LOPRI;
147                     toneLengthMillis = 500;
148                     break;
149                 case TONE_OUT_OF_SERVICE:
150                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
151                     toneVolume = RELATIVE_VOLUME_LOPRI;
152                     toneLengthMillis = 375;
153                     break;
154                 case TONE_REDIAL:
155                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
156                     toneVolume = RELATIVE_VOLUME_LOPRI;
157                     toneLengthMillis = 5000;
158                     break;
159                 case TONE_REORDER:
160                     toneType = ToneGenerator.TONE_CDMA_REORDER;
161                     toneVolume = RELATIVE_VOLUME_HIPRI;
162                     toneLengthMillis = 4000;
163                     break;
164                 case TONE_RING_BACK:
165                     toneType = ToneGenerator.TONE_SUP_RINGTONE;
166                     toneVolume = RELATIVE_VOLUME_HIPRI;
167                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
168                     break;
169                 case TONE_UNOBTAINABLE_NUMBER:
170                     toneType = ToneGenerator.TONE_SUP_ERROR;
171                     toneVolume = RELATIVE_VOLUME_HIPRI;
172                     toneLengthMillis = 4000;
173                     break;
174                 case TONE_VOICE_PRIVACY:
175                     // TODO: fill in.
176                     throw new IllegalStateException("Voice privacy tone NYI.");
177                 default:
178                     throw new IllegalStateException("Bad toneId: " + mToneId);
179             }
180 
181             int stream = AudioManager.STREAM_VOICE_CALL;
182             if (mCallAudioManager.isBluetoothAudioOn()) {
183                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
184             }
185 
186             // If the ToneGenerator creation fails, just continue without it. It is a local audio
187             // signal, and is not as important.
188             try {
189                 Log.v(this, "Creating generator");
190                 toneGenerator = new ToneGenerator(stream, toneVolume);
191             } catch (RuntimeException e) {
192                 Log.w(this, "Failed to create ToneGenerator.", e);
193                 return;
194             }
195 
196             // TODO: Certain CDMA tones need to check the ringer-volume state before
197             // playing. See CallNotifier.InCallTonePlayer.
198 
199             // TODO: Some tones play through the end of a call so we need to inform
200             // CallAudioManager that we want focus the same way that Ringer does.
201 
202             synchronized (this) {
203                 if (mState != STATE_STOPPED) {
204                     mState = STATE_ON;
205                     toneGenerator.startTone(toneType);
206                     try {
207                         Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
208                                 toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
209                         wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
210                     } catch (InterruptedException e) {
211                         Log.w(this, "wait interrupted", e);
212                     }
213                 }
214             }
215             mState = STATE_OFF;
216         } finally {
217             if (toneGenerator != null) {
218                 toneGenerator.release();
219             }
220             cleanUpTonePlayer();
221         }
222     }
223 
startTone()224     void startTone() {
225         ThreadUtil.checkOnMainThread();
226 
227         sTonesPlaying++;
228         if (sTonesPlaying == 1) {
229             mCallAudioManager.setIsTonePlaying(true);
230         }
231 
232         start();
233     }
234 
235     /**
236      * Stops the tone.
237      */
stopTone()238     void stopTone() {
239         synchronized (this) {
240             if (mState == STATE_ON) {
241                 Log.d(this, "Stopping the tone %d.", mToneId);
242                 notify();
243             }
244             mState = STATE_STOPPED;
245         }
246     }
247 
cleanUpTonePlayer()248     private void cleanUpTonePlayer() {
249         // Release focus on the main thread.
250         mMainThreadHandler.post(new Runnable() {
251             @Override public void run() {
252                 if (sTonesPlaying == 0) {
253                     Log.wtf(this, "Over-releasing focus for tone player.");
254                 } else if (--sTonesPlaying == 0) {
255                     mCallAudioManager.setIsTonePlaying(false);
256                 }
257             }
258         });
259     }
260 }
261