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 private final ToneGeneratorFactory mToneGeneratorFactory; 44 Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory)45 Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, 46 TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) { 47 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; 48 mLock = lock; 49 mToneGeneratorFactory = toneGeneratorFactory; 50 } 51 setCallAudioManager(CallAudioManager callAudioManager)52 public void setCallAudioManager(CallAudioManager callAudioManager) { 53 mCallAudioManager = callAudioManager; 54 } 55 createPlayer(int tone)56 public InCallTonePlayer createPlayer(int tone) { 57 return new InCallTonePlayer(tone, mCallAudioManager, 58 mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory); 59 } 60 } 61 62 public interface ToneGeneratorFactory { get(int streamType, int volume)63 ToneGenerator get (int streamType, int volume); 64 } 65 66 // The possible tones that we can play. 67 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; 81 public static final int TONE_VIDEO_UPGRADE = 14; 82 83 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. 89 private static final int TIMEOUT_BUFFER_MILLIS = 20; 90 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; 104 private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; 105 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 114 /** Telecom lock object. */ 115 private final TelecomSystem.SyncRoot mLock; 116 117 private Session mSession; 118 private final Object mSessionLock = new Object(); 119 120 private final ToneGeneratorFactory mToneGenerator; 121 122 /** 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 */ InCallTonePlayer( int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory)127 private InCallTonePlayer( 128 int toneId, 129 CallAudioManager callAudioManager, 130 CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, 131 TelecomSystem.SyncRoot lock, 132 ToneGeneratorFactory toneGeneratorFactory) { 133 mState = STATE_OFF; 134 mToneId = toneId; 135 mCallAudioManager = callAudioManager; 136 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; 137 mLock = lock; 138 mToneGenerator = toneGeneratorFactory; 139 } 140 141 /** {@inheritDoc} */ 142 @Override run()143 public void run() { 144 ToneGenerator toneGenerator = null; 145 try { 146 synchronized (mSessionLock) { 147 if (mSession != null) { 148 Log.continueSession(mSession, "ICTP.r"); 149 mSession = null; 150 } 151 } 152 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. 156 final int toneLengthMillis; 157 158 switch (mToneId) { 159 case TONE_BUSY: 160 // TODO: CDMA-specific tones 161 toneType = ToneGenerator.TONE_SUP_BUSY; 162 toneVolume = RELATIVE_VOLUME_HIPRI; 163 toneLengthMillis = 4000; 164 break; 165 case TONE_CALL_ENDED: 166 toneType = ToneGenerator.TONE_PROP_PROMPT; 167 toneVolume = RELATIVE_VOLUME_HIPRI; 168 toneLengthMillis = 200; 169 break; 170 case TONE_OTA_CALL_ENDED: 171 // TODO: fill in 172 throw new IllegalStateException("OTA Call ended NYI."); 173 case TONE_CALL_WAITING: 174 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 175 toneVolume = RELATIVE_VOLUME_HIPRI; 176 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 177 break; 178 case TONE_CDMA_DROP: 179 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 180 toneVolume = RELATIVE_VOLUME_LOPRI; 181 toneLengthMillis = 375; 182 break; 183 case TONE_CONGESTION: 184 toneType = ToneGenerator.TONE_SUP_CONGESTION; 185 toneVolume = RELATIVE_VOLUME_HIPRI; 186 toneLengthMillis = 4000; 187 break; 188 case TONE_INTERCEPT: 189 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 190 toneVolume = RELATIVE_VOLUME_LOPRI; 191 toneLengthMillis = 500; 192 break; 193 case TONE_OUT_OF_SERVICE: 194 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 195 toneVolume = RELATIVE_VOLUME_LOPRI; 196 toneLengthMillis = 375; 197 break; 198 case TONE_REDIAL: 199 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 200 toneVolume = RELATIVE_VOLUME_LOPRI; 201 toneLengthMillis = 5000; 202 break; 203 case TONE_REORDER: 204 toneType = ToneGenerator.TONE_CDMA_REORDER; 205 toneVolume = RELATIVE_VOLUME_HIPRI; 206 toneLengthMillis = 4000; 207 break; 208 case TONE_RING_BACK: 209 toneType = ToneGenerator.TONE_SUP_RINGTONE; 210 toneVolume = RELATIVE_VOLUME_HIPRI; 211 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 212 break; 213 case TONE_UNOBTAINABLE_NUMBER: 214 toneType = ToneGenerator.TONE_SUP_ERROR; 215 toneVolume = RELATIVE_VOLUME_HIPRI; 216 toneLengthMillis = 4000; 217 break; 218 case TONE_VOICE_PRIVACY: 219 // TODO: fill in. 220 throw new IllegalStateException("Voice privacy tone NYI."); 221 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; 227 default: 228 throw new IllegalStateException("Bad toneId: " + mToneId); 229 } 230 231 int stream = AudioManager.STREAM_VOICE_CALL; 232 if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) { 233 stream = AudioManager.STREAM_BLUETOOTH_SCO; 234 } 235 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"); 240 toneGenerator = mToneGenerator.get(stream, toneVolume); 241 } catch (RuntimeException e) { 242 Log.w(this, "Failed to create ToneGenerator.", e); 243 return; 244 } 245 246 // TODO: Certain CDMA tones need to check the ringer-volume state before 247 // playing. See CallNotifier.InCallTonePlayer. 248 249 // TODO: Some tones play through the end of a call so we need to inform 250 // 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, 258 toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 259 wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 260 } 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(); 271 Log.endSession(); 272 } 273 } 274 275 @VisibleForTesting startTone()276 public void startTone() { 277 sTonesPlaying++; 278 if (sTonesPlaying == 1) { 279 mCallAudioManager.setIsTonePlaying(true); 280 } 281 282 synchronized (mSessionLock) { 283 if (mSession != null) { 284 Log.cancelSubsession(mSession); 285 } 286 mSession = Log.createSubsession(); 287 } 288 289 super.start(); 290 } 291 292 @Override start()293 public void start() { 294 Log.w(this, "Do not call the start method directly; use startTone instead."); 295 } 296 297 /** 298 * Stops the tone. 299 */ 300 @VisibleForTesting stopTone()301 public void stopTone() { 302 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 cleanUpTonePlayer()311 private void cleanUpTonePlayer() { 312 // Release focus on the main thread. 313 mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) { 314 @Override 315 public void loggedRun() { 316 if (sTonesPlaying == 0) { 317 Log.wtf(this, "Over-releasing focus for tone player."); 318 } else if (--sTonesPlaying == 0) { 319 mCallAudioManager.setIsTonePlaying(false); 320 } 321 } 322 }.prepare()); 323 } 324 } 325