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 /** 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 */ 29 public 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 private final TelecomSystem.SyncRoot mLock; 37 38 Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) { 39 mCallAudioManager = callAudioManager; 40 mLock = lock; 41 } 42 43 InCallTonePlayer createPlayer(int tone) { 44 return new InCallTonePlayer(tone, mCallAudioManager, mLock); 45 } 46 } 47 48 // The possible tones that we can play. 49 public static final int TONE_INVALID = 0; 50 public static final int TONE_BUSY = 1; 51 public static final int TONE_CALL_ENDED = 2; 52 public static final int TONE_OTA_CALL_ENDED = 3; 53 public static final int TONE_CALL_WAITING = 4; 54 public static final int TONE_CDMA_DROP = 5; 55 public static final int TONE_CONGESTION = 6; 56 public static final int TONE_INTERCEPT = 7; 57 public static final int TONE_OUT_OF_SERVICE = 8; 58 public static final int TONE_REDIAL = 9; 59 public static final int TONE_REORDER = 10; 60 public static final int TONE_RING_BACK = 11; 61 public static final int TONE_UNOBTAINABLE_NUMBER = 12; 62 public static final int TONE_VOICE_PRIVACY = 13; 63 public static final int TONE_VIDEO_UPGRADE = 14; 64 65 private static final int RELATIVE_VOLUME_EMERGENCY = 100; 66 private static final int RELATIVE_VOLUME_HIPRI = 80; 67 private static final int RELATIVE_VOLUME_LOPRI = 50; 68 69 // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout 70 // value for a tone is exact duration of the tone itself. 71 private static final int TIMEOUT_BUFFER_MILLIS = 20; 72 73 // The tone state. 74 private static final int STATE_OFF = 0; 75 private static final int STATE_ON = 1; 76 private static final int STATE_STOPPED = 2; 77 78 /** 79 * Keeps count of the number of actively playing tones so that we can notify CallAudioManager 80 * when we need focus and when it can be release. This should only be manipulated from the main 81 * thread. 82 */ 83 private static int sTonesPlaying = 0; 84 85 private final CallAudioManager mCallAudioManager; 86 87 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 88 89 /** The ID of the tone to play. */ 90 private final int mToneId; 91 92 /** Current state of the tone player. */ 93 private int mState; 94 95 /** Telecom lock object. */ 96 private final TelecomSystem.SyncRoot mLock; 97 98 /** 99 * Initializes the tone player. Private; use the {@link Factory} to create tone players. 100 * 101 * @param toneId ID of the tone to play, see TONE_* constants. 102 */ 103 private InCallTonePlayer( 104 int toneId, 105 CallAudioManager callAudioManager, 106 TelecomSystem.SyncRoot lock) { 107 mState = STATE_OFF; 108 mToneId = toneId; 109 mCallAudioManager = callAudioManager; 110 mLock = lock; 111 } 112 113 /** {@inheritDoc} */ 114 @Override 115 public void run() { 116 ToneGenerator toneGenerator = null; 117 try { 118 Log.d(this, "run(toneId = %s)", mToneId); 119 120 final int toneType; // Passed to ToneGenerator.startTone. 121 final int toneVolume; // Passed to the ToneGenerator constructor. 122 final int toneLengthMillis; 123 124 switch (mToneId) { 125 case TONE_BUSY: 126 // TODO: CDMA-specific tones 127 toneType = ToneGenerator.TONE_SUP_BUSY; 128 toneVolume = RELATIVE_VOLUME_HIPRI; 129 toneLengthMillis = 4000; 130 break; 131 case TONE_CALL_ENDED: 132 toneType = ToneGenerator.TONE_PROP_PROMPT; 133 toneVolume = RELATIVE_VOLUME_HIPRI; 134 toneLengthMillis = 200; 135 break; 136 case TONE_OTA_CALL_ENDED: 137 // TODO: fill in 138 throw new IllegalStateException("OTA Call ended NYI."); 139 case TONE_CALL_WAITING: 140 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 141 toneVolume = RELATIVE_VOLUME_HIPRI; 142 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 143 break; 144 case TONE_CDMA_DROP: 145 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 146 toneVolume = RELATIVE_VOLUME_LOPRI; 147 toneLengthMillis = 375; 148 break; 149 case TONE_CONGESTION: 150 toneType = ToneGenerator.TONE_SUP_CONGESTION; 151 toneVolume = RELATIVE_VOLUME_HIPRI; 152 toneLengthMillis = 4000; 153 break; 154 case TONE_INTERCEPT: 155 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 156 toneVolume = RELATIVE_VOLUME_LOPRI; 157 toneLengthMillis = 500; 158 break; 159 case TONE_OUT_OF_SERVICE: 160 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 161 toneVolume = RELATIVE_VOLUME_LOPRI; 162 toneLengthMillis = 375; 163 break; 164 case TONE_REDIAL: 165 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 166 toneVolume = RELATIVE_VOLUME_LOPRI; 167 toneLengthMillis = 5000; 168 break; 169 case TONE_REORDER: 170 toneType = ToneGenerator.TONE_CDMA_REORDER; 171 toneVolume = RELATIVE_VOLUME_HIPRI; 172 toneLengthMillis = 4000; 173 break; 174 case TONE_RING_BACK: 175 toneType = ToneGenerator.TONE_SUP_RINGTONE; 176 toneVolume = RELATIVE_VOLUME_HIPRI; 177 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 178 break; 179 case TONE_UNOBTAINABLE_NUMBER: 180 toneType = ToneGenerator.TONE_SUP_ERROR; 181 toneVolume = RELATIVE_VOLUME_HIPRI; 182 toneLengthMillis = 4000; 183 break; 184 case TONE_VOICE_PRIVACY: 185 // TODO: fill in. 186 throw new IllegalStateException("Voice privacy tone NYI."); 187 case TONE_VIDEO_UPGRADE: 188 // Similar to the call waiting tone, but does not repeat. 189 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 190 toneVolume = RELATIVE_VOLUME_HIPRI; 191 toneLengthMillis = 4000; 192 break; 193 default: 194 throw new IllegalStateException("Bad toneId: " + mToneId); 195 } 196 197 int stream = AudioManager.STREAM_VOICE_CALL; 198 if (mCallAudioManager.isBluetoothAudioOn()) { 199 stream = AudioManager.STREAM_BLUETOOTH_SCO; 200 } 201 202 // If the ToneGenerator creation fails, just continue without it. It is a local audio 203 // signal, and is not as important. 204 try { 205 Log.v(this, "Creating generator"); 206 toneGenerator = new ToneGenerator(stream, toneVolume); 207 } catch (RuntimeException e) { 208 Log.w(this, "Failed to create ToneGenerator.", e); 209 return; 210 } 211 212 // TODO: Certain CDMA tones need to check the ringer-volume state before 213 // playing. See CallNotifier.InCallTonePlayer. 214 215 // TODO: Some tones play through the end of a call so we need to inform 216 // CallAudioManager that we want focus the same way that Ringer does. 217 218 synchronized (this) { 219 if (mState != STATE_STOPPED) { 220 mState = STATE_ON; 221 toneGenerator.startTone(toneType); 222 try { 223 Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId, 224 toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 225 wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 226 } catch (InterruptedException e) { 227 Log.w(this, "wait interrupted", e); 228 } 229 } 230 } 231 mState = STATE_OFF; 232 } finally { 233 if (toneGenerator != null) { 234 toneGenerator.release(); 235 } 236 cleanUpTonePlayer(); 237 } 238 } 239 240 void startTone() { 241 sTonesPlaying++; 242 if (sTonesPlaying == 1) { 243 mCallAudioManager.setIsTonePlaying(true); 244 } 245 246 start(); 247 } 248 249 /** 250 * Stops the tone. 251 */ 252 void stopTone() { 253 synchronized (this) { 254 if (mState == STATE_ON) { 255 Log.d(this, "Stopping the tone %d.", mToneId); 256 notify(); 257 } 258 mState = STATE_STOPPED; 259 } 260 } 261 262 private void cleanUpTonePlayer() { 263 // Release focus on the main thread. 264 mMainThreadHandler.post(new Runnable() { 265 @Override public void run() { 266 synchronized (mLock) { 267 if (sTonesPlaying == 0) { 268 Log.wtf(this, "Over-releasing focus for tone player."); 269 } else if (--sTonesPlaying == 0) { 270 mCallAudioManager.setIsTonePlaying(false); 271 } 272 } 273 } 274 }); 275 } 276 } 277