blob: c00e1cfa1e4cd49c635f1ae377ae14b259dff9a2 [file] [log] [blame]
Santos Cordona56f2762014-03-24 15:55:53 -07001/*
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
17package com.android.telecomm;
18
19import android.media.AudioManager;
20import android.media.ToneGenerator;
21import android.os.Handler;
22import 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 */
29public 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}