1 /* 2 * Copyright (C) 2011 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.cellbroadcastreceiver; 18 19 import android.app.Service; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.AssetFileDescriptor; 23 import android.content.res.Resources; 24 import android.media.AudioManager; 25 import android.media.MediaPlayer; 26 import android.media.MediaPlayer.OnErrorListener; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Message; 30 import android.os.Vibrator; 31 import android.speech.tts.TextToSpeech; 32 import android.telephony.PhoneStateListener; 33 import android.telephony.TelephonyManager; 34 import android.util.Log; 35 36 import java.util.Locale; 37 38 import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG; 39 40 /** 41 * Manages alert audio and vibration and text-to-speech. Runs as a service so that 42 * it can continue to play if another activity overrides the CellBroadcastListActivity. 43 */ 44 public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnInitListener, 45 TextToSpeech.OnUtteranceCompletedListener { 46 private static final String TAG = "CellBroadcastAlertAudio"; 47 48 /** Action to start playing alert audio/vibration/speech. */ 49 static final String ACTION_START_ALERT_AUDIO = "ACTION_START_ALERT_AUDIO"; 50 51 /** Extra for alert audio duration (from settings). */ 52 public static final String ALERT_AUDIO_DURATION_EXTRA = 53 "com.android.cellbroadcastreceiver.ALERT_AUDIO_DURATION"; 54 55 /** Extra for message body to speak (if speech enabled in settings). */ 56 public static final String ALERT_AUDIO_MESSAGE_BODY = 57 "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_BODY"; 58 59 /** Extra for text-to-speech language (if speech enabled in settings). */ 60 public static final String ALERT_AUDIO_MESSAGE_LANGUAGE = 61 "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_LANGUAGE"; 62 63 /** Pause duration between alert sound and alert speech. */ 64 private static final int PAUSE_DURATION_BEFORE_SPEAKING_MSEC = 1000; 65 66 /** Vibration uses the same on/off pattern as the CMAS alert tone */ 67 private static final long[] sVibratePattern = new long[] { 0, 2000, 500, 1000, 500, 1000, 500 }; 68 69 private static final int STATE_IDLE = 0; 70 private static final int STATE_ALERTING = 1; 71 private static final int STATE_PAUSING = 2; 72 private static final int STATE_SPEAKING = 3; 73 74 private int mState; 75 76 private TextToSpeech mTts; 77 private boolean mTtsEngineReady; 78 79 private String mMessageBody; 80 private String mMessageLanguage; 81 private boolean mTtsLanguageSupported; 82 83 private Vibrator mVibrator; 84 private MediaPlayer mMediaPlayer; 85 private AudioManager mAudioManager; 86 private TelephonyManager mTelephonyManager; 87 private int mInitialCallState; 88 89 // Internal messages 90 private static final int ALERT_SOUND_FINISHED = 1000; 91 private static final int ALERT_PAUSE_FINISHED = 1001; 92 private final Handler mHandler = new Handler() { 93 @Override 94 public void handleMessage(Message msg) { 95 switch (msg.what) { 96 case ALERT_SOUND_FINISHED: 97 if (DBG) Log.v(TAG, "ALERT_SOUND_FINISHED"); 98 stop(); // stop alert sound 99 // if we can speak the message text 100 if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) { 101 mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED), 102 PAUSE_DURATION_BEFORE_SPEAKING_MSEC); 103 mState = STATE_PAUSING; 104 } else { 105 stopSelf(); 106 mState = STATE_IDLE; 107 } 108 break; 109 110 case ALERT_PAUSE_FINISHED: 111 if (DBG) Log.v(TAG, "ALERT_PAUSE_FINISHED"); 112 if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) { 113 if (DBG) Log.v(TAG, "Speaking broadcast text: " + mMessageBody); 114 mTts.speak(mMessageBody, TextToSpeech.QUEUE_FLUSH, null); 115 mState = STATE_SPEAKING; 116 } else { 117 Log.w(TAG, "TTS engine not ready or language not supported"); 118 stopSelf(); 119 mState = STATE_IDLE; 120 } 121 break; 122 123 default: 124 Log.e(TAG, "Handler received unknown message, what=" + msg.what); 125 } 126 } 127 }; 128 129 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 130 @Override 131 public void onCallStateChanged(int state, String ignored) { 132 // Stop the alert sound and speech if the call state changes. 133 if (state != TelephonyManager.CALL_STATE_IDLE 134 && state != mInitialCallState) { 135 stopSelf(); 136 } 137 } 138 }; 139 140 /** 141 * Callback from TTS engine after initialization. 142 * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. 143 */ 144 public void onInit(int status) { 145 if (DBG) Log.v(TAG, "onInit() TTS engine status: " + status); 146 if (status == TextToSpeech.SUCCESS) { 147 mTtsEngineReady = true; 148 // try to set the TTS language to match the broadcast 149 setTtsLanguage(); 150 } else { 151 mTtsEngineReady = false; 152 mTts = null; 153 Log.e(TAG, "onInit() TTS engine error: " + status); 154 } 155 } 156 157 /** 158 * Try to set the TTS engine language to the value of mMessageLanguage. 159 * mTtsLanguageSupported will be updated based on the response. 160 */ 161 private void setTtsLanguage() { 162 if (mMessageLanguage != null) { 163 if (DBG) Log.v(TAG, "Setting TTS language to '" + mMessageLanguage + '\''); 164 int result = mTts.setLanguage(new Locale(mMessageLanguage)); 165 // success values are >= 0, failure returns negative value 166 if (DBG) Log.v(TAG, "TTS setLanguage() returned: " + result); 167 mTtsLanguageSupported = result >= 0; 168 } else { 169 // try to use the default TTS language for broadcasts with no language specified 170 if (DBG) Log.v(TAG, "No language specified in broadcast: using default"); 171 mTtsLanguageSupported = true; 172 } 173 } 174 175 /** 176 * Callback from TTS engine. 177 * @param utteranceId the identifier of the utterance. 178 */ 179 public void onUtteranceCompleted(String utteranceId) { 180 stopSelf(); 181 } 182 183 @Override 184 public void onCreate() { 185 mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 186 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 187 // Listen for incoming calls to kill the alarm. 188 mTelephonyManager = 189 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 190 mTelephonyManager.listen( 191 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 192 CellBroadcastAlertWakeLock.acquireCpuWakeLock(this); 193 } 194 195 @Override 196 public void onDestroy() { 197 stop(); 198 // Stop listening for incoming calls. 199 mTelephonyManager.listen(mPhoneStateListener, 0); 200 CellBroadcastAlertWakeLock.releaseCpuLock(); 201 // shutdown TTS engine 202 if (mTts != null) { 203 mTts.stop(); 204 mTts.shutdown(); 205 } 206 } 207 208 @Override 209 public IBinder onBind(Intent intent) { 210 return null; 211 } 212 213 @Override 214 public int onStartCommand(Intent intent, int flags, int startId) { 215 // No intent, tell the system not to restart us. 216 if (intent == null) { 217 stopSelf(); 218 return START_NOT_STICKY; 219 } 220 221 // This extra should always be provided by CellBroadcastAlertService, 222 // but default to 4 seconds just to be safe 223 int duration = intent.getIntExtra(ALERT_AUDIO_DURATION_EXTRA, 4); 224 225 // Get text to speak (if enabled by user) 226 mMessageBody = intent.getStringExtra(ALERT_AUDIO_MESSAGE_BODY); 227 mMessageLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_LANGUAGE); 228 229 if (mMessageBody != null) { 230 if (mTts == null) { 231 mTts = new TextToSpeech(this, this); 232 } else if (mTtsEngineReady) { 233 setTtsLanguage(); 234 } 235 } 236 237 play(duration * 1000); // convert to milliseconds 238 239 // Record the initial call state here so that the new alarm has the 240 // newest state. 241 mInitialCallState = mTelephonyManager.getCallState(); 242 243 return START_STICKY; 244 } 245 246 // Volume suggested by media team for in-call alarms. 247 private static final float IN_CALL_VOLUME = 0.125f; 248 249 /** 250 * Start playing the alert sound, and send delayed message when it's time to stop. 251 * @param duration the alert sound duration in milliseconds 252 */ 253 private void play(int duration) { 254 // stop() checks to see if we are already playing. 255 stop(); 256 257 if (DBG) Log.v(TAG, "play()"); 258 259 // future optimization: reuse media player object 260 mMediaPlayer = new MediaPlayer(); 261 mMediaPlayer.setOnErrorListener(new OnErrorListener() { 262 public boolean onError(MediaPlayer mp, int what, int extra) { 263 Log.e(TAG, "Error occurred while playing audio."); 264 mp.stop(); 265 mp.release(); 266 mMediaPlayer = null; 267 return true; 268 } 269 }); 270 271 try { 272 // Check if we are in a call. If we are, play the alert 273 // sound at a low volume to not disrupt the call. 274 if (mTelephonyManager.getCallState() 275 != TelephonyManager.CALL_STATE_IDLE) { 276 Log.v(TAG, "in call: reducing volume"); 277 mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); 278 } 279 setDataSourceFromResource(getResources(), mMediaPlayer, 280 R.raw.attention_signal); 281 mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM, 282 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 283 startAlarm(mMediaPlayer); 284 } catch (Exception ex) { 285 Log.e(TAG, "Failed to play alert sound", ex); 286 } 287 288 /* Start the vibrator after everything is ok with the media player */ 289 mVibrator.vibrate(sVibratePattern, 1); 290 291 // stop alert after the specified duration 292 mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_SOUND_FINISHED), duration); 293 mState = STATE_ALERTING; 294 } 295 296 // Do the common stuff when starting the alarm. 297 private static void startAlarm(MediaPlayer player) 298 throws java.io.IOException, IllegalArgumentException, 299 IllegalStateException { 300 player.setAudioStreamType(AudioManager.STREAM_ALARM); 301 player.setLooping(true); 302 player.prepare(); 303 player.start(); 304 } 305 306 private static void setDataSourceFromResource(Resources resources, 307 MediaPlayer player, int res) throws java.io.IOException { 308 AssetFileDescriptor afd = resources.openRawResourceFd(res); 309 if (afd != null) { 310 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 311 afd.getLength()); 312 afd.close(); 313 } 314 } 315 316 /** 317 * Stops alert audio and speech. 318 */ 319 public void stop() { 320 if (DBG) Log.v(TAG, "stop()"); 321 322 mHandler.removeMessages(ALERT_SOUND_FINISHED); 323 mHandler.removeMessages(ALERT_PAUSE_FINISHED); 324 325 if (mState == STATE_ALERTING) { 326 // Stop audio playing 327 if (mMediaPlayer != null) { 328 mMediaPlayer.stop(); 329 mMediaPlayer.release(); 330 mMediaPlayer = null; 331 } 332 333 // Stop vibrator 334 mVibrator.cancel(); 335 } else if (mState == STATE_SPEAKING && mTts != null) { 336 mTts.stop(); 337 } 338 mAudioManager.abandonAudioFocus(null); 339 mState = STATE_IDLE; 340 } 341 } 342