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 41 Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, 42 TelecomSystem.SyncRoot lock) { 43 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; 44 mLock = lock; 45 } 46 47 public void setCallAudioManager(CallAudioManager callAudioManager) { 48 mCallAudioManager = callAudioManager; 49 } 50 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 */ 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 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 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 super.start(); 277 } 278 279 @Override 280 public void start() { 281 Log.w(this, "Do not call the start method directly; use startTone instead."); 282 } 283 284 /** 285 * Stops the tone. 286 */ 287 @VisibleForTesting 288 public void stopTone() { 289 synchronized (this) { 290 if (mState == STATE_ON) { 291 Log.d(this, "Stopping the tone %d.", mToneId); 292 notify(); 293 } 294 mState = STATE_STOPPED; 295 } 296 } 297 298 private void cleanUpTonePlayer() { 299 // Release focus on the main thread. 300 mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) { 301 @Override 302 public void loggedRun() { 303 if (sTonesPlaying == 0) { 304 Log.wtf(this, "Over-releasing focus for tone player."); 305 } else if (--sTonesPlaying == 0) { 306 mCallAudioManager.setIsTonePlaying(false); 307 } 308 } 309 }.prepare()); 310 } 311 } 312