blob: 5abbf4985d248624195804de272437bb03502ac9 [file] [log] [blame]
Santos Cordon92a2d812014-04-30 15:19:01 -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
Tyler Gunn7cc70b42014-09-12 22:17:27 -070017package com.android.server.telecom;
Santos Cordon92a2d812014-04-30 15:19:01 -070018
Tyler Gunn91d43cf2014-09-17 12:19:39 -070019import android.content.Context;
Santos Cordon92a2d812014-04-30 15:19:01 -070020import android.media.AudioManager;
21import android.media.ToneGenerator;
Roshan Pius9bb88e92015-06-08 16:46:11 -070022import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Message;
Santos Cordon92a2d812014-04-30 15:19:01 -070025import android.provider.Settings;
Brad Ebinger953e1af2016-10-05 15:45:22 -070026import android.telecom.Log;
Santos Cordon92a2d812014-04-30 15:19:01 -070027
Roshan Pius9bb88e92015-06-08 16:46:11 -070028import com.android.internal.util.Preconditions;
29
Tyler Gunn91d43cf2014-09-17 12:19:39 -070030// TODO: Needed for move to system service: import com.android.internal.R;
Santos Cordon92a2d812014-04-30 15:19:01 -070031
32/**
33 * Plays DTMF tones locally for the caller to hear. In order to reduce (1) the amount of times we
34 * check the "play local tones" setting and (2) the length of time we keep the tone generator, this
35 * class employs a concept of a call "session" that starts and stops when the foreground call
36 * changes.
37 */
Hall Liue792b332016-04-19 14:23:09 -070038public class DtmfLocalTonePlayer {
Santos Cordon92a2d812014-04-30 15:19:01 -070039 /** Generator used to actually play the tone. */
40 private ToneGenerator mToneGenerator;
41
42 /** The current call associated with an existing dtmf session. */
43 private Call mCall;
44
Roshan Pius9bb88e92015-06-08 16:46:11 -070045 /**
46 * Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator
47 * thread.
48 */
49 private static final int EVENT_CREATE_OBJECT = 1;
50 private static final int EVENT_DELETE_OBJECT = 2;
51
52 /** Handler running on the tonegenerator thread. */
53 private Handler mHandler;
54
Hall Liue091ab92015-12-18 17:05:30 -080055 public DtmfLocalTonePlayer() { }
Roshan Pius9bb88e92015-06-08 16:46:11 -070056
Santos Cordon92a2d812014-04-30 15:19:01 -070057 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
Jay Shrauneracb91eb2014-08-08 16:04:53 -070058 endDtmfSession(oldForegroundCall);
59 startDtmfSession(newForegroundCall);
Santos Cordon92a2d812014-04-30 15:19:01 -070060 }
61
62 /**
63 * Starts playing the dtmf tone specified by c.
64 *
65 * @param call The associated call.
66 * @param c The digit to play.
67 */
68 void playTone(Call call, char c) {
69 // Do nothing if it is not the right call.
70 if (mCall != call) {
71 return;
72 }
Roshan Pius9bb88e92015-06-08 16:46:11 -070073 synchronized(this) {
74 if (mToneGenerator == null) {
75 Log.d(this, "playTone: mToneGenerator == null, %c.", c);
76 } else {
77 Log.d(this, "starting local tone: %c.", c);
78 int tone = getMappedTone(c);
79 if (tone != ToneGenerator.TONE_UNKNOWN) {
80 mToneGenerator.startTone(tone, -1 /* toneDuration */);
81 }
Santos Cordon92a2d812014-04-30 15:19:01 -070082 }
83 }
84 }
85
86 /**
87 * Stops any currently playing dtmf tone.
88 *
89 * @param call The associated call.
90 */
91 void stopTone(Call call) {
92 // Do nothing if it's not the right call.
93 if (mCall != call) {
94 return;
95 }
Roshan Pius9bb88e92015-06-08 16:46:11 -070096 synchronized(this) {
97 if (mToneGenerator == null) {
98 Log.d(this, "stopTone: mToneGenerator == null.");
99 } else {
100 Log.d(this, "stopping local tone.");
101 mToneGenerator.stopTone();
102 }
Santos Cordon92a2d812014-04-30 15:19:01 -0700103 }
104 }
105
106 /**
107 * Runs initialization requires to play local tones during a call.
108 *
109 * @param call The call associated with this dtmf session.
110 */
111 private void startDtmfSession(Call call) {
Jay Shrauneracb91eb2014-08-08 16:04:53 -0700112 if (call == null) {
113 return;
114 }
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700115 final Context context = call.getContext();
Santos Cordon92a2d812014-04-30 15:19:01 -0700116 final boolean areLocalTonesEnabled;
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700117 if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
Santos Cordon92a2d812014-04-30 15:19:01 -0700118 areLocalTonesEnabled = Settings.System.getInt(
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700119 context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
Santos Cordon92a2d812014-04-30 15:19:01 -0700120 } else {
121 areLocalTonesEnabled = false;
122 }
123
124 mCall = call;
125
126 if (areLocalTonesEnabled) {
Roshan Pius9bb88e92015-06-08 16:46:11 -0700127 Log.d(this, "Posting create.");
128 postMessage(EVENT_CREATE_OBJECT);
Santos Cordon92a2d812014-04-30 15:19:01 -0700129 }
130 }
131
132 /**
133 * Releases resources needed for playing local dtmf tones.
134 *
135 * @param call The call associated with the session to end.
136 */
137 private void endDtmfSession(Call call) {
Jay Shrauneracb91eb2014-08-08 16:04:53 -0700138 if (call != null && mCall == call) {
Santos Cordon92a2d812014-04-30 15:19:01 -0700139 // Do a stopTone() in case the sessions ends before we are told to stop the tone.
140 stopTone(call);
141
142 mCall = null;
Roshan Pius9bb88e92015-06-08 16:46:11 -0700143 Log.d(this, "Posting delete.");
144 postMessage(EVENT_DELETE_OBJECT);
145 }
146 }
Santos Cordon92a2d812014-04-30 15:19:01 -0700147
Roshan Pius9bb88e92015-06-08 16:46:11 -0700148 /**
149 * Posts a message to the tonegenerator-thread handler. Creates the handler if the handler
150 * has not been instantiated.
151 *
152 * @param messageCode The message to post.
153 */
154 private void postMessage(int messageCode) {
155 synchronized(this) {
156 if (mHandler == null) {
157 mHandler = getNewHandler();
158 }
159
160 if (mHandler == null) {
161 Log.d(this, "Message %d skipped because there is no handler.", messageCode);
162 } else {
163 mHandler.obtainMessage(messageCode, null).sendToTarget();
Santos Cordon92a2d812014-04-30 15:19:01 -0700164 }
165 }
166 }
Santos Cordon4bc02452014-11-19 15:51:12 -0800167
Roshan Pius9bb88e92015-06-08 16:46:11 -0700168 /**
169 * Creates a new tonegenerator Handler running in its own thread.
170 */
171 private Handler getNewHandler() {
172 Preconditions.checkState(mHandler == null);
173
174 HandlerThread thread = new HandlerThread("tonegenerator-dtmf");
175 thread.start();
176
177 return new Handler(thread.getLooper()) {
178 @Override
179 public void handleMessage(Message msg) {
180 switch(msg.what) {
181 case EVENT_CREATE_OBJECT:
182 synchronized(DtmfLocalTonePlayer.this) {
183 if (mToneGenerator == null) {
184 try {
185 mToneGenerator = new ToneGenerator(
186 AudioManager.STREAM_DTMF, 80);
187 } catch (RuntimeException e) {
188 Log.e(this, e, "Error creating local tone generator.");
189 mToneGenerator = null;
190 }
191 }
192 }
193 break;
194 case EVENT_DELETE_OBJECT:
195 synchronized(DtmfLocalTonePlayer.this) {
196 if (mToneGenerator != null) {
197 mToneGenerator.release();
198 mToneGenerator = null;
199 }
200 // Delete the handler after the tone generator object is deleted by
201 // the tonegenerator thread.
202 if (mHandler != null && !mHandler.hasMessages(EVENT_CREATE_OBJECT)) {
203 // Stop the handler only if there are no pending CREATE messages.
204 mHandler.removeMessages(EVENT_DELETE_OBJECT);
205 mHandler.getLooper().quitSafely();
206 mHandler = null;
207 }
208 }
209 break;
210 }
211 }
212 };
213 }
214
Hall Liue091ab92015-12-18 17:05:30 -0800215 private static int getMappedTone(char digit) {
Santos Cordon4bc02452014-11-19 15:51:12 -0800216 if (digit >= '0' && digit <= '9') {
217 return ToneGenerator.TONE_DTMF_0 + digit - '0';
218 } else if (digit == '#') {
219 return ToneGenerator.TONE_DTMF_P;
220 } else if (digit == '*') {
221 return ToneGenerator.TONE_DTMF_S;
222 }
223 return ToneGenerator.TONE_UNKNOWN;
224 }
Santos Cordon92a2d812014-04-30 15:19:01 -0700225}