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