blob: 7f085879d7f2d9129095523c52e21a325679b3dd [file] [log] [blame]
Santos Cordona56f2762014-03-24 15:55:53 -07001/*
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.telecomm;
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
37 Factory(CallAudioManager callAudioManager) {
38 mCallAudioManager = callAudioManager;
39 }
40
41 InCallTonePlayer createPlayer(int tone) {
42 return new InCallTonePlayer(tone, mCallAudioManager);
43 }
44 }
45
46 // The possible tones that we can play.
Santos Cordonc7b8eba2014-04-01 15:26:28 -070047 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;
Santos Cordona56f2762014-03-24 15:55:53 -070061
Santos Cordona56f2762014-03-24 15:55:53 -070062 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.
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070068 private static final int TIMEOUT_BUFFER_MILLIS = 20;
Santos Cordona56f2762014-03-24 15:55:53 -070069
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 */
97 private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) {
98 mState = STATE_OFF;
99 mToneId = toneId;
100 mCallAudioManager = callAudioManager;
101 }
102
103 /** {@inheritDoc} */
104 @Override
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.
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700112 final int toneLengthMillis;
Santos Cordona56f2762014-03-24 15:55:53 -0700113
114 switch (mToneId) {
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700115 case TONE_BUSY:
116 // TODO: CDMA-specific tones
117 toneType = ToneGenerator.TONE_SUP_BUSY;
118 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700119 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700120 break;
121 case TONE_CALL_ENDED:
122 toneType = ToneGenerator.TONE_PROP_PROMPT;
123 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700124 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700125 break;
126 case TONE_OTA_CALL_ENDED:
127 // TODO: fill in
128 throw new IllegalStateException("OTA Call ended NYI.");
129 case TONE_CALL_WAITING:
Santos Cordon40f78c22014-04-07 02:11:42 -0700130 toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
131 toneVolume = RELATIVE_VOLUME_HIPRI;
132 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
133 break;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700134 case TONE_CDMA_DROP:
135 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
136 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700137 toneLengthMillis = 375;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700138 break;
139 case TONE_CONGESTION:
140 toneType = ToneGenerator.TONE_SUP_CONGESTION;
141 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700142 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700143 break;
144 case TONE_INTERCEPT:
145 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
146 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700147 toneLengthMillis = 500;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700148 break;
149 case TONE_OUT_OF_SERVICE:
150 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
151 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700152 toneLengthMillis = 375;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700153 break;
154 case TONE_REDIAL:
155 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
156 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700157 toneLengthMillis = 5000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700158 break;
159 case TONE_REORDER:
160 toneType = ToneGenerator.TONE_CDMA_REORDER;
161 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700162 toneLengthMillis = 5000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700163 break;
Santos Cordona56f2762014-03-24 15:55:53 -0700164 case TONE_RING_BACK:
165 toneType = ToneGenerator.TONE_SUP_RINGTONE;
166 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700167 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
Santos Cordona56f2762014-03-24 15:55:53 -0700168 break;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700169 case TONE_UNOBTAINABLE_NUMBER:
170 toneType = ToneGenerator.TONE_SUP_ERROR;
171 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700172 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700173 break;
174 case TONE_VOICE_PRIVACY:
175 // TODO: fill in.
176 throw new IllegalStateException("Voice privacy tone NYI.");
Santos Cordona56f2762014-03-24 15:55:53 -0700177 default:
178 throw new IllegalStateException("Bad toneId: " + mToneId);
179 }
180
Santos Cordona56f2762014-03-24 15:55:53 -0700181 int stream = AudioManager.STREAM_VOICE_CALL;
Santos Cordonc7e85d42014-05-22 02:51:48 -0700182 if (mCallAudioManager.isBluetoothAudioOn()) {
183 stream = AudioManager.STREAM_BLUETOOTH_SCO;
184 }
Santos Cordona56f2762014-03-24 15:55:53 -0700185
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(santoscordon): Certain CDMA tones need to check the ringer-volume state before
197 // playing. See CallNotifier.InCallTonePlayer.
198
199 // TODO(santoscordon): 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,
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700208 toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
209 wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
Santos Cordona56f2762014-03-24 15:55:53 -0700210 } 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
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 */
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
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}