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