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.server.telecom; 18 19 import android.content.Context; 20 import android.media.AudioManager; 21 import android.media.ToneGenerator; 22 import android.os.Handler; 23 import android.os.HandlerThread; 24 import android.os.Message; 25 import android.provider.Settings; 26 27 import com.android.internal.util.Preconditions; 28 29 // TODO: Needed for move to system service: import com.android.internal.R; 30 31 /** 32 * Plays DTMF tones locally for the caller to hear. In order to reduce (1) the amount of times we 33 * check the "play local tones" setting and (2) the length of time we keep the tone generator, this 34 * class employs a concept of a call "session" that starts and stops when the foreground call 35 * changes. 36 */ 37 public class DtmfLocalTonePlayer { 38 /** Generator used to actually play the tone. */ 39 private ToneGenerator mToneGenerator; 40 41 /** The current call associated with an existing dtmf session. */ 42 private Call mCall; 43 44 /** 45 * Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator 46 * thread. 47 */ 48 private static final int EVENT_CREATE_OBJECT = 1; 49 private static final int EVENT_DELETE_OBJECT = 2; 50 51 /** Handler running on the tonegenerator thread. */ 52 private Handler mHandler; 53 DtmfLocalTonePlayer()54 public DtmfLocalTonePlayer() { } 55 onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall)56 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { 57 endDtmfSession(oldForegroundCall); 58 startDtmfSession(newForegroundCall); 59 } 60 61 /** 62 * Starts playing the dtmf tone specified by c. 63 * 64 * @param call The associated call. 65 * @param c The digit to play. 66 */ playTone(Call call, char c)67 void playTone(Call call, char c) { 68 // Do nothing if it is not the right call. 69 if (mCall != call) { 70 return; 71 } 72 synchronized(this) { 73 if (mToneGenerator == null) { 74 Log.d(this, "playTone: mToneGenerator == null, %c.", c); 75 } else { 76 Log.d(this, "starting local tone: %c.", c); 77 int tone = getMappedTone(c); 78 if (tone != ToneGenerator.TONE_UNKNOWN) { 79 mToneGenerator.startTone(tone, -1 /* toneDuration */); 80 } 81 } 82 } 83 } 84 85 /** 86 * Stops any currently playing dtmf tone. 87 * 88 * @param call The associated call. 89 */ stopTone(Call call)90 void stopTone(Call call) { 91 // Do nothing if it's not the right call. 92 if (mCall != call) { 93 return; 94 } 95 synchronized(this) { 96 if (mToneGenerator == null) { 97 Log.d(this, "stopTone: mToneGenerator == null."); 98 } else { 99 Log.d(this, "stopping local tone."); 100 mToneGenerator.stopTone(); 101 } 102 } 103 } 104 105 /** 106 * Runs initialization requires to play local tones during a call. 107 * 108 * @param call The call associated with this dtmf session. 109 */ startDtmfSession(Call call)110 private void startDtmfSession(Call call) { 111 if (call == null) { 112 return; 113 } 114 final Context context = call.getContext(); 115 final boolean areLocalTonesEnabled; 116 if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) { 117 areLocalTonesEnabled = Settings.System.getInt( 118 context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 119 } else { 120 areLocalTonesEnabled = false; 121 } 122 123 mCall = call; 124 125 if (areLocalTonesEnabled) { 126 Log.d(this, "Posting create."); 127 postMessage(EVENT_CREATE_OBJECT); 128 } 129 } 130 131 /** 132 * Releases resources needed for playing local dtmf tones. 133 * 134 * @param call The call associated with the session to end. 135 */ endDtmfSession(Call call)136 private void endDtmfSession(Call call) { 137 if (call != null && mCall == call) { 138 // Do a stopTone() in case the sessions ends before we are told to stop the tone. 139 stopTone(call); 140 141 mCall = null; 142 Log.d(this, "Posting delete."); 143 postMessage(EVENT_DELETE_OBJECT); 144 } 145 } 146 147 /** 148 * Posts a message to the tonegenerator-thread handler. Creates the handler if the handler 149 * has not been instantiated. 150 * 151 * @param messageCode The message to post. 152 */ postMessage(int messageCode)153 private void postMessage(int messageCode) { 154 synchronized(this) { 155 if (mHandler == null) { 156 mHandler = getNewHandler(); 157 } 158 159 if (mHandler == null) { 160 Log.d(this, "Message %d skipped because there is no handler.", messageCode); 161 } else { 162 mHandler.obtainMessage(messageCode, null).sendToTarget(); 163 } 164 } 165 } 166 167 /** 168 * Creates a new tonegenerator Handler running in its own thread. 169 */ getNewHandler()170 private Handler getNewHandler() { 171 Preconditions.checkState(mHandler == null); 172 173 HandlerThread thread = new HandlerThread("tonegenerator-dtmf"); 174 thread.start(); 175 176 return new Handler(thread.getLooper()) { 177 @Override 178 public void handleMessage(Message msg) { 179 switch(msg.what) { 180 case EVENT_CREATE_OBJECT: 181 synchronized(DtmfLocalTonePlayer.this) { 182 if (mToneGenerator == null) { 183 try { 184 mToneGenerator = new ToneGenerator( 185 AudioManager.STREAM_DTMF, 80); 186 } catch (RuntimeException e) { 187 Log.e(this, e, "Error creating local tone generator."); 188 mToneGenerator = null; 189 } 190 } 191 } 192 break; 193 case EVENT_DELETE_OBJECT: 194 synchronized(DtmfLocalTonePlayer.this) { 195 if (mToneGenerator != null) { 196 mToneGenerator.release(); 197 mToneGenerator = null; 198 } 199 // Delete the handler after the tone generator object is deleted by 200 // the tonegenerator thread. 201 if (mHandler != null && !mHandler.hasMessages(EVENT_CREATE_OBJECT)) { 202 // Stop the handler only if there are no pending CREATE messages. 203 mHandler.removeMessages(EVENT_DELETE_OBJECT); 204 mHandler.getLooper().quitSafely(); 205 mHandler = null; 206 } 207 } 208 break; 209 } 210 } 211 }; 212 } 213 214 private static int getMappedTone(char digit) { 215 if (digit >= '0' && digit <= '9') { 216 return ToneGenerator.TONE_DTMF_0 + digit - '0'; 217 } else if (digit == '#') { 218 return ToneGenerator.TONE_DTMF_P; 219 } else if (digit == '*') { 220 return ToneGenerator.TONE_DTMF_S; 221 } 222 return ToneGenerator.TONE_UNKNOWN; 223 } 224 } 225