blob: a258aeef27133001e247db96138b645e10483db1 [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
Tyler Gunn7cc70b42014-09-12 22:17:27 -070017package com.android.server.telecom;
Santos Cordona56f2762014-03-24 15:55:53 -070018
19import android.media.AudioManager;
20import android.media.ToneGenerator;
21import android.os.Handler;
22import android.os.Looper;
Brad Ebingera3eccfe2016-10-05 15:45:22 -070023import android.telecom.Log;
24import android.telecom.Logging.Runnable;
25import android.telecom.Logging.Session;
Santos Cordona56f2762014-03-24 15:55:53 -070026
Brad Ebingerd931a012015-10-21 12:54:08 -070027import com.android.internal.annotations.VisibleForTesting;
28
Santos Cordona56f2762014-03-24 15:55:53 -070029/**
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 */
Brad Ebingerd931a012015-10-21 12:54:08 -070034public class InCallTonePlayer extends Thread {
Santos Cordona56f2762014-03-24 15:55:53 -070035
36 /**
37 * Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
38 */
39 public static class Factory {
Hall Liue091ab92015-12-18 17:05:30 -080040 private CallAudioManager mCallAudioManager;
Hall Liuf62630a2015-10-27 14:53:39 -070041 private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
Ihab Awadabcbce42015-04-07 14:04:01 -070042 private final TelecomSystem.SyncRoot mLock;
Brad Ebinger7bba1112017-06-08 13:57:28 -070043 private final ToneGeneratorFactory mToneGeneratorFactory;
Santos Cordona56f2762014-03-24 15:55:53 -070044
Hall Liue091ab92015-12-18 17:05:30 -080045 Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
Brad Ebinger7bba1112017-06-08 13:57:28 -070046 TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) {
Hall Liuf62630a2015-10-27 14:53:39 -070047 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
Ihab Awadabcbce42015-04-07 14:04:01 -070048 mLock = lock;
Brad Ebinger7bba1112017-06-08 13:57:28 -070049 mToneGeneratorFactory = toneGeneratorFactory;
Santos Cordona56f2762014-03-24 15:55:53 -070050 }
51
Hall Liue091ab92015-12-18 17:05:30 -080052 public void setCallAudioManager(CallAudioManager callAudioManager) {
53 mCallAudioManager = callAudioManager;
54 }
55
Brad Ebingerd931a012015-10-21 12:54:08 -070056 public InCallTonePlayer createPlayer(int tone) {
Hall Liuf62630a2015-10-27 14:53:39 -070057 return new InCallTonePlayer(tone, mCallAudioManager,
Brad Ebinger7bba1112017-06-08 13:57:28 -070058 mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory);
Santos Cordona56f2762014-03-24 15:55:53 -070059 }
60 }
61
Brad Ebinger7bba1112017-06-08 13:57:28 -070062 public interface ToneGeneratorFactory {
63 ToneGenerator get (int streamType, int volume);
64 }
65
Santos Cordona56f2762014-03-24 15:55:53 -070066 // The possible tones that we can play.
Santos Cordonc7b8eba2014-04-01 15:26:28 -070067 public static final int TONE_INVALID = 0;
68 public static final int TONE_BUSY = 1;
69 public static final int TONE_CALL_ENDED = 2;
70 public static final int TONE_OTA_CALL_ENDED = 3;
71 public static final int TONE_CALL_WAITING = 4;
72 public static final int TONE_CDMA_DROP = 5;
73 public static final int TONE_CONGESTION = 6;
74 public static final int TONE_INTERCEPT = 7;
75 public static final int TONE_OUT_OF_SERVICE = 8;
76 public static final int TONE_REDIAL = 9;
77 public static final int TONE_REORDER = 10;
78 public static final int TONE_RING_BACK = 11;
79 public static final int TONE_UNOBTAINABLE_NUMBER = 12;
80 public static final int TONE_VOICE_PRIVACY = 13;
Tyler Gunn86014fc2015-06-12 14:31:25 -070081 public static final int TONE_VIDEO_UPGRADE = 14;
Santos Cordona56f2762014-03-24 15:55:53 -070082
Santos Cordona56f2762014-03-24 15:55:53 -070083 private static final int RELATIVE_VOLUME_EMERGENCY = 100;
84 private static final int RELATIVE_VOLUME_HIPRI = 80;
85 private static final int RELATIVE_VOLUME_LOPRI = 50;
86
87 // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
88 // value for a tone is exact duration of the tone itself.
Sailesh Nepal8c85dee2014-04-07 22:21:40 -070089 private static final int TIMEOUT_BUFFER_MILLIS = 20;
Santos Cordona56f2762014-03-24 15:55:53 -070090
91 // The tone state.
92 private static final int STATE_OFF = 0;
93 private static final int STATE_ON = 1;
94 private static final int STATE_STOPPED = 2;
95
96 /**
97 * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
98 * when we need focus and when it can be release. This should only be manipulated from the main
99 * thread.
100 */
101 private static int sTonesPlaying = 0;
102
103 private final CallAudioManager mCallAudioManager;
Hall Liuf62630a2015-10-27 14:53:39 -0700104 private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
Santos Cordona56f2762014-03-24 15:55:53 -0700105
106 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
107
108 /** The ID of the tone to play. */
109 private final int mToneId;
110
111 /** Current state of the tone player. */
112 private int mState;
113
Ihab Awadabcbce42015-04-07 14:04:01 -0700114 /** Telecom lock object. */
115 private final TelecomSystem.SyncRoot mLock;
116
Hall Liud8ca5452016-03-10 14:53:15 -0800117 private Session mSession;
118 private final Object mSessionLock = new Object();
119
Brad Ebinger7bba1112017-06-08 13:57:28 -0700120 private final ToneGeneratorFactory mToneGenerator;
121
Santos Cordona56f2762014-03-24 15:55:53 -0700122 /**
123 * Initializes the tone player. Private; use the {@link Factory} to create tone players.
124 *
125 * @param toneId ID of the tone to play, see TONE_* constants.
126 */
Ihab Awadabcbce42015-04-07 14:04:01 -0700127 private InCallTonePlayer(
128 int toneId,
129 CallAudioManager callAudioManager,
Hall Liuf62630a2015-10-27 14:53:39 -0700130 CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
Brad Ebinger7bba1112017-06-08 13:57:28 -0700131 TelecomSystem.SyncRoot lock,
132 ToneGeneratorFactory toneGeneratorFactory) {
Santos Cordona56f2762014-03-24 15:55:53 -0700133 mState = STATE_OFF;
134 mToneId = toneId;
135 mCallAudioManager = callAudioManager;
Hall Liuf62630a2015-10-27 14:53:39 -0700136 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
Ihab Awadabcbce42015-04-07 14:04:01 -0700137 mLock = lock;
Brad Ebinger7bba1112017-06-08 13:57:28 -0700138 mToneGenerator = toneGeneratorFactory;
Santos Cordona56f2762014-03-24 15:55:53 -0700139 }
140
141 /** {@inheritDoc} */
142 @Override
143 public void run() {
144 ToneGenerator toneGenerator = null;
145 try {
Hall Liud8ca5452016-03-10 14:53:15 -0800146 synchronized (mSessionLock) {
147 if (mSession != null) {
148 Log.continueSession(mSession, "ICTP.r");
149 mSession = null;
150 }
151 }
Santos Cordona56f2762014-03-24 15:55:53 -0700152 Log.d(this, "run(toneId = %s)", mToneId);
153
154 final int toneType; // Passed to ToneGenerator.startTone.
155 final int toneVolume; // Passed to the ToneGenerator constructor.
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700156 final int toneLengthMillis;
Santos Cordona56f2762014-03-24 15:55:53 -0700157
158 switch (mToneId) {
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700159 case TONE_BUSY:
160 // TODO: CDMA-specific tones
161 toneType = ToneGenerator.TONE_SUP_BUSY;
162 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700163 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700164 break;
165 case TONE_CALL_ENDED:
166 toneType = ToneGenerator.TONE_PROP_PROMPT;
167 toneVolume = RELATIVE_VOLUME_HIPRI;
Yorke Lee7062bb92014-10-13 09:54:15 -0700168 toneLengthMillis = 200;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700169 break;
170 case TONE_OTA_CALL_ENDED:
171 // TODO: fill in
172 throw new IllegalStateException("OTA Call ended NYI.");
173 case TONE_CALL_WAITING:
Santos Cordon40f78c22014-04-07 02:11:42 -0700174 toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
175 toneVolume = RELATIVE_VOLUME_HIPRI;
176 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
177 break;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700178 case TONE_CDMA_DROP:
179 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
180 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700181 toneLengthMillis = 375;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700182 break;
183 case TONE_CONGESTION:
184 toneType = ToneGenerator.TONE_SUP_CONGESTION;
185 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700186 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700187 break;
188 case TONE_INTERCEPT:
189 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
190 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700191 toneLengthMillis = 500;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700192 break;
193 case TONE_OUT_OF_SERVICE:
194 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
195 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700196 toneLengthMillis = 375;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700197 break;
198 case TONE_REDIAL:
199 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
200 toneVolume = RELATIVE_VOLUME_LOPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700201 toneLengthMillis = 5000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700202 break;
203 case TONE_REORDER:
204 toneType = ToneGenerator.TONE_CDMA_REORDER;
205 toneVolume = RELATIVE_VOLUME_HIPRI;
Yorke Lee7062bb92014-10-13 09:54:15 -0700206 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700207 break;
Santos Cordona56f2762014-03-24 15:55:53 -0700208 case TONE_RING_BACK:
209 toneType = ToneGenerator.TONE_SUP_RINGTONE;
210 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700211 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
Santos Cordona56f2762014-03-24 15:55:53 -0700212 break;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700213 case TONE_UNOBTAINABLE_NUMBER:
214 toneType = ToneGenerator.TONE_SUP_ERROR;
215 toneVolume = RELATIVE_VOLUME_HIPRI;
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700216 toneLengthMillis = 4000;
Santos Cordonc7b8eba2014-04-01 15:26:28 -0700217 break;
218 case TONE_VOICE_PRIVACY:
219 // TODO: fill in.
220 throw new IllegalStateException("Voice privacy tone NYI.");
Tyler Gunn86014fc2015-06-12 14:31:25 -0700221 case TONE_VIDEO_UPGRADE:
222 // Similar to the call waiting tone, but does not repeat.
223 toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
224 toneVolume = RELATIVE_VOLUME_HIPRI;
225 toneLengthMillis = 4000;
226 break;
Santos Cordona56f2762014-03-24 15:55:53 -0700227 default:
228 throw new IllegalStateException("Bad toneId: " + mToneId);
229 }
230
Santos Cordona56f2762014-03-24 15:55:53 -0700231 int stream = AudioManager.STREAM_VOICE_CALL;
Hall Liuf62630a2015-10-27 14:53:39 -0700232 if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
Santos Cordonc7e85d42014-05-22 02:51:48 -0700233 stream = AudioManager.STREAM_BLUETOOTH_SCO;
234 }
Santos Cordona56f2762014-03-24 15:55:53 -0700235
236 // If the ToneGenerator creation fails, just continue without it. It is a local audio
237 // signal, and is not as important.
238 try {
239 Log.v(this, "Creating generator");
Brad Ebinger7bba1112017-06-08 13:57:28 -0700240 toneGenerator = mToneGenerator.get(stream, toneVolume);
Santos Cordona56f2762014-03-24 15:55:53 -0700241 } catch (RuntimeException e) {
242 Log.w(this, "Failed to create ToneGenerator.", e);
243 return;
244 }
245
Santos Cordondf399862014-08-06 04:39:15 -0700246 // TODO: Certain CDMA tones need to check the ringer-volume state before
Santos Cordona56f2762014-03-24 15:55:53 -0700247 // playing. See CallNotifier.InCallTonePlayer.
248
Santos Cordondf399862014-08-06 04:39:15 -0700249 // TODO: Some tones play through the end of a call so we need to inform
Santos Cordona56f2762014-03-24 15:55:53 -0700250 // CallAudioManager that we want focus the same way that Ringer does.
251
252 synchronized (this) {
253 if (mState != STATE_STOPPED) {
254 mState = STATE_ON;
255 toneGenerator.startTone(toneType);
256 try {
257 Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
Sailesh Nepal8c85dee2014-04-07 22:21:40 -0700258 toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
259 wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
Santos Cordona56f2762014-03-24 15:55:53 -0700260 } catch (InterruptedException e) {
261 Log.w(this, "wait interrupted", e);
262 }
263 }
264 }
265 mState = STATE_OFF;
266 } finally {
267 if (toneGenerator != null) {
268 toneGenerator.release();
269 }
270 cleanUpTonePlayer();
Brad Ebingerefa8d2a2016-04-01 11:23:05 -0700271 Log.endSession();
Santos Cordona56f2762014-03-24 15:55:53 -0700272 }
273 }
Brad Ebingerd931a012015-10-21 12:54:08 -0700274
275 @VisibleForTesting
276 public void startTone() {
Santos Cordona56f2762014-03-24 15:55:53 -0700277 sTonesPlaying++;
278 if (sTonesPlaying == 1) {
279 mCallAudioManager.setIsTonePlaying(true);
280 }
281
Hall Liud8ca5452016-03-10 14:53:15 -0800282 synchronized (mSessionLock) {
283 if (mSession != null) {
284 Log.cancelSubsession(mSession);
285 }
286 mSession = Log.createSubsession();
287 }
288
Hall Liub626c022016-06-23 13:16:51 -0700289 super.start();
290 }
291
292 @Override
293 public void start() {
294 Log.w(this, "Do not call the start method directly; use startTone instead.");
Santos Cordona56f2762014-03-24 15:55:53 -0700295 }
296
297 /**
298 * Stops the tone.
299 */
Brad Ebingerd931a012015-10-21 12:54:08 -0700300 @VisibleForTesting
301 public void stopTone() {
Santos Cordona56f2762014-03-24 15:55:53 -0700302 synchronized (this) {
303 if (mState == STATE_ON) {
304 Log.d(this, "Stopping the tone %d.", mToneId);
305 notify();
306 }
307 mState = STATE_STOPPED;
308 }
309 }
310
311 private void cleanUpTonePlayer() {
312 // Release focus on the main thread.
Brad Ebingerf5e06662016-08-25 16:16:27 -0700313 mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
Brad Ebingere62e9e82016-02-01 18:26:40 -0800314 @Override
315 public void loggedRun() {
Brad Ebingerf5e06662016-08-25 16:16:27 -0700316 if (sTonesPlaying == 0) {
317 Log.wtf(this, "Over-releasing focus for tone player.");
318 } else if (--sTonesPlaying == 0) {
319 mCallAudioManager.setIsTonePlaying(false);
Santos Cordona56f2762014-03-24 15:55:53 -0700320 }
321 }
Brad Ebingere62e9e82016-02-01 18:26:40 -0800322 }.prepare());
Santos Cordona56f2762014-03-24 15:55:53 -0700323 }
324}