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 import android.telecom.Log;
24 import android.telecom.Logging.Runnable;
25 import android.telecom.Logging.Session;
26 
27 import 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  */
34 public 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 
Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock)44         Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
45                 TelecomSystem.SyncRoot lock) {
46             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
47             mLock = lock;
48         }
49 
setCallAudioManager(CallAudioManager callAudioManager)50         public void setCallAudioManager(CallAudioManager callAudioManager) {
51             mCallAudioManager = callAudioManager;
52         }
53 
createPlayer(int tone)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      */
InCallTonePlayer( int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock)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
run()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
startTone()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
start()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
stopTone()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 
cleanUpTonePlayer()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