Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 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.telecomm; |
| 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 | |
| 37 | Factory(CallAudioManager callAudioManager) { |
| 38 | mCallAudioManager = callAudioManager; |
| 39 | } |
| 40 | |
| 41 | InCallTonePlayer createPlayer(int tone) { |
| 42 | return new InCallTonePlayer(tone, mCallAudioManager); |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | // The possible tones that we can play. |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 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; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 61 | |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 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. |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 68 | private static final int TIMEOUT_BUFFER_MILLIS = 20; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 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 | */ |
| 97 | private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) { |
| 98 | mState = STATE_OFF; |
| 99 | mToneId = toneId; |
| 100 | mCallAudioManager = callAudioManager; |
| 101 | } |
| 102 | |
| 103 | /** {@inheritDoc} */ |
| 104 | @Override |
| 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. |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 112 | final int toneLengthMillis; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 113 | |
| 114 | switch (mToneId) { |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 115 | case TONE_BUSY: |
| 116 | // TODO: CDMA-specific tones |
| 117 | toneType = ToneGenerator.TONE_SUP_BUSY; |
| 118 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 119 | toneLengthMillis = 4000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 120 | break; |
| 121 | case TONE_CALL_ENDED: |
| 122 | toneType = ToneGenerator.TONE_PROP_PROMPT; |
| 123 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 124 | toneLengthMillis = 4000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 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: |
Santos Cordon | 40f78c2 | 2014-04-07 02:11:42 -0700 | [diff] [blame] | 130 | toneType = ToneGenerator.TONE_SUP_CALL_WAITING; |
| 131 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 132 | toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; |
| 133 | break; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 134 | case TONE_CDMA_DROP: |
| 135 | toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; |
| 136 | toneVolume = RELATIVE_VOLUME_LOPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 137 | toneLengthMillis = 375; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 138 | break; |
| 139 | case TONE_CONGESTION: |
| 140 | toneType = ToneGenerator.TONE_SUP_CONGESTION; |
| 141 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 142 | toneLengthMillis = 4000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 143 | break; |
| 144 | case TONE_INTERCEPT: |
| 145 | toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; |
| 146 | toneVolume = RELATIVE_VOLUME_LOPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 147 | toneLengthMillis = 500; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 148 | break; |
| 149 | case TONE_OUT_OF_SERVICE: |
| 150 | toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; |
| 151 | toneVolume = RELATIVE_VOLUME_LOPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 152 | toneLengthMillis = 375; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 153 | break; |
| 154 | case TONE_REDIAL: |
| 155 | toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; |
| 156 | toneVolume = RELATIVE_VOLUME_LOPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 157 | toneLengthMillis = 5000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 158 | break; |
| 159 | case TONE_REORDER: |
| 160 | toneType = ToneGenerator.TONE_CDMA_REORDER; |
| 161 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 162 | toneLengthMillis = 5000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 163 | break; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 164 | case TONE_RING_BACK: |
| 165 | toneType = ToneGenerator.TONE_SUP_RINGTONE; |
| 166 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 167 | toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 168 | break; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 169 | case TONE_UNOBTAINABLE_NUMBER: |
| 170 | toneType = ToneGenerator.TONE_SUP_ERROR; |
| 171 | toneVolume = RELATIVE_VOLUME_HIPRI; |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 172 | toneLengthMillis = 4000; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame] | 173 | break; |
| 174 | case TONE_VOICE_PRIVACY: |
| 175 | // TODO: fill in. |
| 176 | throw new IllegalStateException("Voice privacy tone NYI."); |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 177 | default: |
| 178 | throw new IllegalStateException("Bad toneId: " + mToneId); |
| 179 | } |
| 180 | |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 181 | int stream = AudioManager.STREAM_VOICE_CALL; |
Santos Cordon | c7e85d4 | 2014-05-22 02:51:48 -0700 | [diff] [blame] | 182 | if (mCallAudioManager.isBluetoothAudioOn()) { |
| 183 | stream = AudioManager.STREAM_BLUETOOTH_SCO; |
| 184 | } |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 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(santoscordon): Certain CDMA tones need to check the ringer-volume state before |
| 197 | // playing. See CallNotifier.InCallTonePlayer. |
| 198 | |
| 199 | // TODO(santoscordon): 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, |
Sailesh Nepal | 8c85dee | 2014-04-07 22:21:40 -0700 | [diff] [blame] | 208 | toneLengthMillis + TIMEOUT_BUFFER_MILLIS); |
| 209 | wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS); |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 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 | |
| 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 | */ |
| 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 | |
| 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 | } |