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