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. |
| 68 | private static final int TIMEOUT_BUFFER_MS = 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 | */ |
| 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. |
| 112 | final int toneLengthMs; |
| 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; |
| 119 | toneLengthMs = 4000; |
| 120 | break; |
| 121 | case TONE_CALL_ENDED: |
| 122 | toneType = ToneGenerator.TONE_PROP_PROMPT; |
| 123 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 124 | toneLengthMs = 4000; |
| 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 | // TODO: fill in. |
| 131 | throw new IllegalStateException("Call waiting NYI."); |
| 132 | case TONE_CDMA_DROP: |
| 133 | toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; |
| 134 | toneVolume = RELATIVE_VOLUME_LOPRI; |
| 135 | toneLengthMs = 375; |
| 136 | break; |
| 137 | case TONE_CONGESTION: |
| 138 | toneType = ToneGenerator.TONE_SUP_CONGESTION; |
| 139 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 140 | toneLengthMs = 4000; |
| 141 | break; |
| 142 | case TONE_INTERCEPT: |
| 143 | toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; |
| 144 | toneVolume = RELATIVE_VOLUME_LOPRI; |
| 145 | toneLengthMs = 500; |
| 146 | break; |
| 147 | case TONE_OUT_OF_SERVICE: |
| 148 | toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; |
| 149 | toneVolume = RELATIVE_VOLUME_LOPRI; |
| 150 | toneLengthMs = 375; |
| 151 | break; |
| 152 | case TONE_REDIAL: |
| 153 | toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; |
| 154 | toneVolume = RELATIVE_VOLUME_LOPRI; |
| 155 | toneLengthMs = 5000; |
| 156 | break; |
| 157 | case TONE_REORDER: |
| 158 | toneType = ToneGenerator.TONE_CDMA_REORDER; |
| 159 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 160 | toneLengthMs = 5000; |
| 161 | break; |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 162 | case TONE_RING_BACK: |
| 163 | toneType = ToneGenerator.TONE_SUP_RINGTONE; |
| 164 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 165 | toneLengthMs = Integer.MAX_VALUE - TIMEOUT_BUFFER_MS; |
| 166 | break; |
Santos Cordon | c7b8eba | 2014-04-01 15:26:28 -0700 | [diff] [blame^] | 167 | case TONE_UNOBTAINABLE_NUMBER: |
| 168 | toneType = ToneGenerator.TONE_SUP_ERROR; |
| 169 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 170 | toneLengthMs = 4000; |
| 171 | break; |
| 172 | case TONE_VOICE_PRIVACY: |
| 173 | // TODO: fill in. |
| 174 | throw new IllegalStateException("Voice privacy tone NYI."); |
Santos Cordon | a56f276 | 2014-03-24 15:55:53 -0700 | [diff] [blame] | 175 | default: |
| 176 | throw new IllegalStateException("Bad toneId: " + mToneId); |
| 177 | } |
| 178 | |
| 179 | // TODO(santoscordon): Bluetooth should be set manually (STREAM_BLUETOOTH_SCO) for tone |
| 180 | // generator. |
| 181 | int stream = AudioManager.STREAM_VOICE_CALL; |
| 182 | |
| 183 | // If the ToneGenerator creation fails, just continue without it. It is a local audio |
| 184 | // signal, and is not as important. |
| 185 | try { |
| 186 | Log.v(this, "Creating generator"); |
| 187 | toneGenerator = new ToneGenerator(stream, toneVolume); |
| 188 | } catch (RuntimeException e) { |
| 189 | Log.w(this, "Failed to create ToneGenerator.", e); |
| 190 | return; |
| 191 | } |
| 192 | |
| 193 | // TODO(santoscordon): Certain CDMA tones need to check the ringer-volume state before |
| 194 | // playing. See CallNotifier.InCallTonePlayer. |
| 195 | |
| 196 | // TODO(santoscordon): Some tones play through the end of a call so we need to inform |
| 197 | // CallAudioManager that we want focus the same way that Ringer does. |
| 198 | |
| 199 | synchronized (this) { |
| 200 | if (mState != STATE_STOPPED) { |
| 201 | mState = STATE_ON; |
| 202 | toneGenerator.startTone(toneType); |
| 203 | try { |
| 204 | Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId, |
| 205 | toneLengthMs + TIMEOUT_BUFFER_MS); |
| 206 | wait(toneLengthMs + TIMEOUT_BUFFER_MS); |
| 207 | } catch (InterruptedException e) { |
| 208 | Log.w(this, "wait interrupted", e); |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | mState = STATE_OFF; |
| 213 | } finally { |
| 214 | if (toneGenerator != null) { |
| 215 | toneGenerator.release(); |
| 216 | } |
| 217 | cleanUpTonePlayer(); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | void startTone() { |
| 222 | ThreadUtil.checkOnMainThread(); |
| 223 | |
| 224 | sTonesPlaying++; |
| 225 | if (sTonesPlaying == 1) { |
| 226 | mCallAudioManager.setIsTonePlaying(true); |
| 227 | } |
| 228 | |
| 229 | start(); |
| 230 | } |
| 231 | |
| 232 | /** |
| 233 | * Stops the tone. |
| 234 | */ |
| 235 | void stopTone() { |
| 236 | synchronized (this) { |
| 237 | if (mState == STATE_ON) { |
| 238 | Log.d(this, "Stopping the tone %d.", mToneId); |
| 239 | notify(); |
| 240 | } |
| 241 | mState = STATE_STOPPED; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | private void cleanUpTonePlayer() { |
| 246 | // Release focus on the main thread. |
| 247 | mMainThreadHandler.post(new Runnable() { |
| 248 | @Override public void run() { |
| 249 | if (sTonesPlaying == 0) { |
| 250 | Log.wtf(this, "Over-releasing focus for tone player."); |
| 251 | } else if (--sTonesPlaying == 0) { |
| 252 | mCallAudioManager.setIsTonePlaying(false); |
| 253 | } |
| 254 | } |
| 255 | }); |
| 256 | } |
| 257 | } |