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.Looper; 25 import android.os.Message; 26 import android.provider.Settings; 27 import android.telecom.Log; 28 import android.telecom.Logging.Session; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.Preconditions; 32 33 // TODO: Needed for move to system service: import com.android.internal.R; 34 35 /** 36 * Plays DTMF tones locally for the caller to hear. In order to reduce (1) the amount of times we 37 * check the "play local tones" setting and (2) the length of time we keep the tone generator, this 38 * class employs a concept of a call "session" that starts and stops when the foreground call 39 * changes. 40 */ 41 public class DtmfLocalTonePlayer { 42 public static class ToneGeneratorProxy { 43 /** Generator used to actually play the tone. */ 44 private ToneGenerator mToneGenerator; 45 create()46 public void create() { 47 if (mToneGenerator == null) { 48 try { 49 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80); 50 } catch (RuntimeException e) { 51 Log.e(this, e, "Error creating local tone generator."); 52 mToneGenerator = null; 53 } 54 } 55 } 56 release()57 public void release() { 58 if (mToneGenerator != null) { 59 mToneGenerator.release(); 60 mToneGenerator = null; 61 } 62 } 63 isPresent()64 public boolean isPresent() { 65 return mToneGenerator != null; 66 } 67 startTone(int toneType, int durationMs)68 public void startTone(int toneType, int durationMs) { 69 if (mToneGenerator != null) { 70 mToneGenerator.startTone(toneType, durationMs); 71 } 72 } 73 stopTone()74 public void stopTone() { 75 if (mToneGenerator != null) { 76 mToneGenerator.stopTone(); 77 } 78 } 79 } 80 81 private final class ToneHandler extends Handler { 82 ToneHandler(Looper looper)83 public ToneHandler(Looper looper) { 84 super(looper); 85 } 86 87 @Override handleMessage(Message msg)88 public void handleMessage(Message msg) { 89 if (msg.obj instanceof Session) { 90 Log.continueSession((Session) msg.obj, "DLTP.TH"); 91 } 92 93 switch(msg.what) { 94 case EVENT_START_SESSION: 95 mToneGeneratorProxy.create(); 96 break; 97 case EVENT_END_SESSION: 98 mToneGeneratorProxy.release(); 99 break; 100 case EVENT_PLAY_TONE: 101 char c = (char) msg.arg1; 102 if (!mToneGeneratorProxy.isPresent()) { 103 Log.d(this, "playTone: no tone generator, %c.", c); 104 } else { 105 Log.d(this, "starting local tone: %c.", c); 106 int tone = getMappedTone(c); 107 if (tone != ToneGenerator.TONE_UNKNOWN) { 108 mToneGeneratorProxy.startTone(tone, -1 /* toneDuration */); 109 } 110 } 111 break; 112 case EVENT_STOP_TONE: 113 if (mToneGeneratorProxy.isPresent()) { 114 mToneGeneratorProxy.stopTone(); 115 } 116 break; 117 default: 118 Log.w(this, "Unknown message: %d", msg.what); 119 break; 120 } 121 } 122 } 123 124 /** The current call associated with an existing dtmf session. */ 125 private Call mCall; 126 127 /** 128 * Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator 129 * thread, as well as for actually playing the tones. 130 */ 131 private static final int EVENT_START_SESSION = 1; 132 private static final int EVENT_END_SESSION = 2; 133 private static final int EVENT_PLAY_TONE = 3; 134 private static final int EVENT_STOP_TONE = 4; 135 136 /** Handler running on the tonegenerator thread. */ 137 private ToneHandler mHandler; 138 139 private final ToneGeneratorProxy mToneGeneratorProxy; 140 DtmfLocalTonePlayer(ToneGeneratorProxy toneGeneratorProxy)141 public DtmfLocalTonePlayer(ToneGeneratorProxy toneGeneratorProxy) { 142 mToneGeneratorProxy = toneGeneratorProxy; 143 } 144 onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall)145 public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) { 146 endDtmfSession(oldForegroundCall); 147 startDtmfSession(newForegroundCall); 148 } 149 150 /** 151 * Starts playing the dtmf tone specified by c. 152 * 153 * @param call The associated call. 154 * @param c The digit to play. 155 */ playTone(Call call, char c)156 public void playTone(Call call, char c) { 157 // Do nothing if it is not the right call. 158 if (mCall != call) { 159 return; 160 } 161 162 getHandler().sendMessage( 163 getHandler().obtainMessage(EVENT_PLAY_TONE, (int) c, 0, Log.createSubsession())); 164 } 165 166 /** 167 * Stops any currently playing dtmf tone. 168 * 169 * @param call The associated call. 170 */ stopTone(Call call)171 public void stopTone(Call call) { 172 // Do nothing if it's not the right call. 173 if (mCall != call) { 174 return; 175 } 176 177 getHandler().sendMessage( 178 getHandler().obtainMessage(EVENT_STOP_TONE, Log.createSubsession())); 179 } 180 181 /** 182 * Runs initialization requires to play local tones during a call. 183 * 184 * @param call The call associated with this dtmf session. 185 */ startDtmfSession(Call call)186 private void startDtmfSession(Call call) { 187 if (call == null) { 188 return; 189 } 190 final Context context = call.getContext(); 191 final boolean areLocalTonesEnabled; 192 if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) { 193 areLocalTonesEnabled = Settings.System.getInt( 194 context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1; 195 } else { 196 areLocalTonesEnabled = false; 197 } 198 199 mCall = call; 200 201 if (areLocalTonesEnabled) { 202 Log.d(this, "Posting create."); 203 getHandler().sendMessage( 204 getHandler().obtainMessage(EVENT_START_SESSION, Log.createSubsession())); 205 } 206 } 207 208 /** 209 * Releases resources needed for playing local dtmf tones. 210 * 211 * @param call The call associated with the session to end. 212 */ endDtmfSession(Call call)213 private void endDtmfSession(Call call) { 214 if (call != null && mCall == call) { 215 // Do a stopTone() in case the sessions ends before we are told to stop the tone. 216 stopTone(call); 217 218 mCall = null; 219 Log.d(this, "Posting delete."); 220 getHandler().sendMessage( 221 getHandler().obtainMessage(EVENT_END_SESSION, Log.createSubsession())); 222 } 223 } 224 225 /** 226 * Creates a new ToneHandler on a separate thread if none exists, and returns it. 227 * No need for locking, since everything that calls this is protected by the Telecom lock. 228 */ 229 @VisibleForTesting getHandler()230 public ToneHandler getHandler() { 231 if (mHandler == null) { 232 HandlerThread thread = new HandlerThread("tonegenerator-dtmf"); 233 thread.start(); 234 mHandler = new ToneHandler(thread.getLooper()); 235 } 236 return mHandler; 237 } 238 getMappedTone(char digit)239 private static int getMappedTone(char digit) { 240 if (digit >= '0' && digit <= '9') { 241 return ToneGenerator.TONE_DTMF_0 + digit - '0'; 242 } else if (digit == '#') { 243 return ToneGenerator.TONE_DTMF_P; 244 } else if (digit == '*') { 245 return ToneGenerator.TONE_DTMF_S; 246 } 247 return ToneGenerator.TONE_UNKNOWN; 248 } 249 } 250