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. |
| 47 | public static final int TONE_NONE = 0; |
| 48 | public static final int TONE_RING_BACK = 1; |
| 49 | |
| 50 | // The tone volume relative to other sounds in the stream. |
| 51 | private static final int RELATIVE_VOLUME_EMERGENCY = 100; |
| 52 | private static final int RELATIVE_VOLUME_HIPRI = 80; |
| 53 | private static final int RELATIVE_VOLUME_LOPRI = 50; |
| 54 | |
| 55 | // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout |
| 56 | // value for a tone is exact duration of the tone itself. |
| 57 | private static final int TIMEOUT_BUFFER_MS = 20; |
| 58 | |
| 59 | // The tone state. |
| 60 | private static final int STATE_OFF = 0; |
| 61 | private static final int STATE_ON = 1; |
| 62 | private static final int STATE_STOPPED = 2; |
| 63 | |
| 64 | /** |
| 65 | * Keeps count of the number of actively playing tones so that we can notify CallAudioManager |
| 66 | * when we need focus and when it can be release. This should only be manipulated from the main |
| 67 | * thread. |
| 68 | */ |
| 69 | private static int sTonesPlaying = 0; |
| 70 | |
| 71 | private final CallAudioManager mCallAudioManager; |
| 72 | |
| 73 | private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); |
| 74 | |
| 75 | /** The ID of the tone to play. */ |
| 76 | private final int mToneId; |
| 77 | |
| 78 | /** Current state of the tone player. */ |
| 79 | private int mState; |
| 80 | |
| 81 | /** |
| 82 | * Initializes the tone player. Private; use the {@link Factory} to create tone players. |
| 83 | * |
| 84 | * @param toneId ID of the tone to play, see TONE_* constants. |
| 85 | */ |
| 86 | private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) { |
| 87 | mState = STATE_OFF; |
| 88 | mToneId = toneId; |
| 89 | mCallAudioManager = callAudioManager; |
| 90 | } |
| 91 | |
| 92 | /** {@inheritDoc} */ |
| 93 | @Override |
| 94 | public void run() { |
| 95 | ToneGenerator toneGenerator = null; |
| 96 | try { |
| 97 | Log.d(this, "run(toneId = %s)", mToneId); |
| 98 | |
| 99 | final int toneType; // Passed to ToneGenerator.startTone. |
| 100 | final int toneVolume; // Passed to the ToneGenerator constructor. |
| 101 | final int toneLengthMs; |
| 102 | |
| 103 | switch (mToneId) { |
| 104 | case TONE_RING_BACK: |
| 105 | toneType = ToneGenerator.TONE_SUP_RINGTONE; |
| 106 | toneVolume = RELATIVE_VOLUME_HIPRI; |
| 107 | toneLengthMs = Integer.MAX_VALUE - TIMEOUT_BUFFER_MS; |
| 108 | break; |
| 109 | default: |
| 110 | throw new IllegalStateException("Bad toneId: " + mToneId); |
| 111 | } |
| 112 | |
| 113 | // TODO(santoscordon): Bluetooth should be set manually (STREAM_BLUETOOTH_SCO) for tone |
| 114 | // generator. |
| 115 | int stream = AudioManager.STREAM_VOICE_CALL; |
| 116 | |
| 117 | // If the ToneGenerator creation fails, just continue without it. It is a local audio |
| 118 | // signal, and is not as important. |
| 119 | try { |
| 120 | Log.v(this, "Creating generator"); |
| 121 | toneGenerator = new ToneGenerator(stream, toneVolume); |
| 122 | } catch (RuntimeException e) { |
| 123 | Log.w(this, "Failed to create ToneGenerator.", e); |
| 124 | return; |
| 125 | } |
| 126 | |
| 127 | // TODO(santoscordon): Certain CDMA tones need to check the ringer-volume state before |
| 128 | // playing. See CallNotifier.InCallTonePlayer. |
| 129 | |
| 130 | // TODO(santoscordon): Some tones play through the end of a call so we need to inform |
| 131 | // CallAudioManager that we want focus the same way that Ringer does. |
| 132 | |
| 133 | synchronized (this) { |
| 134 | if (mState != STATE_STOPPED) { |
| 135 | mState = STATE_ON; |
| 136 | toneGenerator.startTone(toneType); |
| 137 | try { |
| 138 | Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId, |
| 139 | toneLengthMs + TIMEOUT_BUFFER_MS); |
| 140 | wait(toneLengthMs + TIMEOUT_BUFFER_MS); |
| 141 | } catch (InterruptedException e) { |
| 142 | Log.w(this, "wait interrupted", e); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | mState = STATE_OFF; |
| 147 | } finally { |
| 148 | if (toneGenerator != null) { |
| 149 | toneGenerator.release(); |
| 150 | } |
| 151 | cleanUpTonePlayer(); |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | void startTone() { |
| 156 | ThreadUtil.checkOnMainThread(); |
| 157 | |
| 158 | sTonesPlaying++; |
| 159 | if (sTonesPlaying == 1) { |
| 160 | mCallAudioManager.setIsTonePlaying(true); |
| 161 | } |
| 162 | |
| 163 | start(); |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Stops the tone. |
| 168 | */ |
| 169 | void stopTone() { |
| 170 | synchronized (this) { |
| 171 | if (mState == STATE_ON) { |
| 172 | Log.d(this, "Stopping the tone %d.", mToneId); |
| 173 | notify(); |
| 174 | } |
| 175 | mState = STATE_STOPPED; |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | private void cleanUpTonePlayer() { |
| 180 | // Release focus on the main thread. |
| 181 | mMainThreadHandler.post(new Runnable() { |
| 182 | @Override public void run() { |
| 183 | if (sTonesPlaying == 0) { |
| 184 | Log.wtf(this, "Over-releasing focus for tone player."); |
| 185 | } else if (--sTonesPlaying == 0) { |
| 186 | mCallAudioManager.setIsTonePlaying(false); |
| 187 | } |
| 188 | } |
| 189 | }); |
| 190 | } |
| 191 | } |