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 static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG; 20 import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.VDBG; 21 22 import android.app.PendingIntent; 23 import android.app.Service; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.AssetFileDescriptor; 27 import android.content.res.Resources; 28 import android.media.AudioManager; 29 import android.media.MediaPlayer; 30 import android.media.MediaPlayer.OnCompletionListener; 31 import android.media.MediaPlayer.OnErrorListener; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Message; 36 import android.os.Vibrator; 37 import android.speech.tts.TextToSpeech; 38 import android.telephony.PhoneStateListener; 39 import android.telephony.TelephonyManager; 40 import android.util.Log; 41 42 import java.util.Locale; 43 import java.util.MissingResourceException; 44 45 /** 46 * Manages alert audio and vibration and text-to-speech. Runs as a service so that 47 * it can continue to play if another activity overrides the CellBroadcastListActivity. 48 */ 49 public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnInitListener, 50 TextToSpeech.OnUtteranceCompletedListener { 51 private static final String TAG = "CellBroadcastAlertAudio"; 52 53 /** Action to start playing alert audio/vibration/speech. */ 54 static final String ACTION_START_ALERT_AUDIO = "ACTION_START_ALERT_AUDIO"; 55 56 /** Extra for message body to speak (if speech enabled in settings). */ 57 public static final String ALERT_AUDIO_MESSAGE_BODY = 58 "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_BODY"; 59 60 /** Extra for text-to-speech preferred language (if speech enabled in settings). */ 61 public static final String ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE = 62 "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE"; 63 64 /** Extra for text-to-speech default language when preferred language is 65 not available (if speech enabled in settings). */ 66 public static final String ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE = 67 "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE"; 68 69 /** Extra for alert tone type */ 70 public static final String ALERT_AUDIO_TONE_TYPE = 71 "com.android.cellbroadcastreceiver.ALERT_AUDIO_TONE_TYPE"; 72 73 /** Extra for alert audio vibration enabled (from settings). */ 74 public static final String ALERT_AUDIO_VIBRATE_EXTRA = 75 "com.android.cellbroadcastreceiver.ALERT_AUDIO_VIBRATE"; 76 77 /** Extra for alert audio ETWS behavior (always vibrate, even in silent mode). */ 78 public static final String ALERT_AUDIO_ETWS_VIBRATE_EXTRA = 79 "com.android.cellbroadcastreceiver.ALERT_AUDIO_ETWS_VIBRATE"; 80 81 private static final String TTS_UTTERANCE_ID = "com.android.cellbroadcastreceiver.UTTERANCE_ID"; 82 83 /** Pause duration between alert sound and alert speech. */ 84 private static final int PAUSE_DURATION_BEFORE_SPEAKING_MSEC = 1000; 85 86 private static final int STATE_IDLE = 0; 87 private static final int STATE_ALERTING = 1; 88 private static final int STATE_PAUSING = 2; 89 private static final int STATE_SPEAKING = 3; 90 91 private int mState; 92 93 private TextToSpeech mTts; 94 private boolean mTtsEngineReady; 95 96 private String mMessageBody; 97 private String mMessagePreferredLanguage; 98 private String mMessageDefaultLanguage; 99 private boolean mTtsLanguageSupported; 100 private boolean mEnableVibrate; 101 private boolean mEnableAudio; 102 103 private Vibrator mVibrator; 104 private MediaPlayer mMediaPlayer; 105 private AudioManager mAudioManager; 106 private TelephonyManager mTelephonyManager; 107 private int mInitialCallState; 108 109 private PendingIntent mPlayReminderIntent; 110 111 public enum ToneType { 112 CMAS_DEFAULT, 113 ETWS_DEFAULT, 114 EARTHQUAKE, 115 TSUNAMI, 116 OTHER 117 } 118 119 // Internal messages 120 private static final int ALERT_SOUND_FINISHED = 1000; 121 private static final int ALERT_PAUSE_FINISHED = 1001; 122 private final Handler mHandler = new Handler() { 123 @Override 124 public void handleMessage(Message msg) { 125 switch (msg.what) { 126 case ALERT_SOUND_FINISHED: 127 if (DBG) log("ALERT_SOUND_FINISHED"); 128 stop(); // stop alert sound 129 // if we can speak the message text 130 if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) { 131 mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED), 132 PAUSE_DURATION_BEFORE_SPEAKING_MSEC); 133 mState = STATE_PAUSING; 134 } else { 135 if (DBG) log("MessageEmpty = " + (mMessageBody == null) + 136 ", mTtsEngineReady = " + mTtsEngineReady + 137 ", mTtsLanguageSupported = " + mTtsLanguageSupported); 138 stopSelf(); 139 mState = STATE_IDLE; 140 } 141 break; 142 143 case ALERT_PAUSE_FINISHED: 144 if (DBG) log("ALERT_PAUSE_FINISHED"); 145 int res = TextToSpeech.ERROR; 146 if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) { 147 if (DBG) log("Speaking broadcast text: " + mMessageBody); 148 149 Bundle params = new Bundle(); 150 // Play TTS in notification stream. 151 params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, 152 AudioManager.STREAM_NOTIFICATION); 153 // Use the non-public parameter 2 --> TextToSpeech.QUEUE_DESTROY for TTS. 154 // The entire playback queue is purged. This is different from QUEUE_FLUSH 155 // in that all entries are purged, not just entries from a given caller. 156 // This is for emergency so we want to kill all other TTS sessions. 157 res = mTts.speak(mMessageBody, 2, params, TTS_UTTERANCE_ID); 158 mState = STATE_SPEAKING; 159 } 160 if (res != TextToSpeech.SUCCESS) { 161 loge("TTS engine not ready or language not supported or speak() failed"); 162 stopSelf(); 163 mState = STATE_IDLE; 164 } 165 break; 166 167 default: 168 loge("Handler received unknown message, what=" + msg.what); 169 } 170 } 171 }; 172 173 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 174 @Override 175 public void onCallStateChanged(int state, String ignored) { 176 // Stop the alert sound and speech if the call state changes. 177 if (state != TelephonyManager.CALL_STATE_IDLE 178 && state != mInitialCallState) { 179 stopSelf(); 180 } 181 } 182 }; 183 184 /** 185 * Callback from TTS engine after initialization. 186 * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. 187 */ 188 @Override onInit(int status)189 public void onInit(int status) { 190 if (VDBG) log("onInit() TTS engine status: " + status); 191 if (status == TextToSpeech.SUCCESS) { 192 mTtsEngineReady = true; 193 mTts.setOnUtteranceCompletedListener(this); 194 // try to set the TTS language to match the broadcast 195 setTtsLanguage(); 196 } else { 197 mTtsEngineReady = false; 198 mTts = null; 199 loge("onInit() TTS engine error: " + status); 200 } 201 } 202 203 /** 204 * Try to set the TTS engine language to the preferred language. If failed, set 205 * it to the default language. mTtsLanguageSupported will be updated based on the response. 206 */ setTtsLanguage()207 private void setTtsLanguage() { 208 209 String language = mMessagePreferredLanguage; 210 if (language == null || language.isEmpty() || 211 TextToSpeech.LANG_AVAILABLE != mTts.isLanguageAvailable(new Locale(language))) { 212 language = mMessageDefaultLanguage; 213 if (language == null || language.isEmpty() || 214 TextToSpeech.LANG_AVAILABLE != mTts.isLanguageAvailable(new Locale(language))) { 215 mTtsLanguageSupported = false; 216 return; 217 } 218 if (DBG) log("Language '" + mMessagePreferredLanguage + "' is not available, using" + 219 "the default language '" + mMessageDefaultLanguage + "'"); 220 } 221 222 if (DBG) log("Setting TTS language to '" + language + '\''); 223 224 try { 225 int result = mTts.setLanguage(new Locale(language)); 226 if (DBG) log("TTS setLanguage() returned: " + result); 227 mTtsLanguageSupported = (result == TextToSpeech.LANG_AVAILABLE); 228 } 229 catch (MissingResourceException e) { 230 mTtsLanguageSupported = false; 231 loge("Language '" + language + "' is not available."); 232 } 233 } 234 235 /** 236 * Callback from TTS engine. 237 * @param utteranceId the identifier of the utterance. 238 */ 239 @Override onUtteranceCompleted(String utteranceId)240 public void onUtteranceCompleted(String utteranceId) { 241 if (utteranceId.equals(TTS_UTTERANCE_ID)) { 242 // When we reach here, it could be TTS completed or TTS was cut due to another 243 // new alert started playing. We don't want to stop the service in the later case. 244 if (mState == STATE_SPEAKING) { 245 stopSelf(); 246 } 247 } 248 } 249 250 @Override onCreate()251 public void onCreate() { 252 mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); 253 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 254 // Listen for incoming calls to kill the alarm. 255 mTelephonyManager = 256 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 257 mTelephonyManager.listen( 258 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 259 } 260 261 @Override onDestroy()262 public void onDestroy() { 263 // stop audio, vibration and TTS 264 stop(); 265 // Stop listening for incoming calls. 266 mTelephonyManager.listen(mPhoneStateListener, 0); 267 // shutdown TTS engine 268 if (mTts != null) { 269 try { 270 mTts.shutdown(); 271 } catch (IllegalStateException e) { 272 // catch "Unable to retrieve AudioTrack pointer for stop()" exception 273 loge("exception trying to shutdown text-to-speech"); 274 } 275 } 276 if (mEnableAudio) { 277 // Release the audio focus so other audio (e.g. music) can resume. 278 // Do not do this in stop() because stop() is also called when we stop the tone (before 279 // TTS is playing). We only want to release the focus when tone and TTS are played. 280 mAudioManager.abandonAudioFocus(null); 281 } 282 // release CPU wake lock acquired by CellBroadcastAlertService 283 CellBroadcastAlertWakeLock.releaseCpuLock(); 284 } 285 286 @Override onBind(Intent intent)287 public IBinder onBind(Intent intent) { 288 return null; 289 } 290 291 @Override onStartCommand(Intent intent, int flags, int startId)292 public int onStartCommand(Intent intent, int flags, int startId) { 293 // No intent, tell the system not to restart us. 294 if (intent == null) { 295 stopSelf(); 296 return START_NOT_STICKY; 297 } 298 299 // Get text to speak (if enabled by user) 300 mMessageBody = intent.getStringExtra(ALERT_AUDIO_MESSAGE_BODY); 301 mMessagePreferredLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE); 302 mMessageDefaultLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE); 303 304 // retrieve the vibrate settings from cellbroadcast receiver settings. 305 mEnableVibrate = intent.getBooleanExtra(ALERT_AUDIO_VIBRATE_EXTRA, true); 306 307 switch (mAudioManager.getRingerMode()) { 308 case AudioManager.RINGER_MODE_SILENT: 309 if (DBG) log("Ringer mode: silent"); 310 mEnableAudio = false; 311 // If the device is in silent mode, do not vibrate (except ETWS). 312 if (!intent.getBooleanExtra(ALERT_AUDIO_ETWS_VIBRATE_EXTRA, false)) { 313 mEnableVibrate = false; 314 } 315 break; 316 case AudioManager.RINGER_MODE_VIBRATE: 317 if (DBG) log("Ringer mode: vibrate"); 318 mEnableAudio = false; 319 break; 320 case AudioManager.RINGER_MODE_NORMAL: 321 default: 322 if (DBG) log("Ringer mode: normal"); 323 mEnableAudio = true; 324 break; 325 } 326 327 if (mMessageBody != null && mEnableAudio) { 328 if (mTts == null) { 329 mTts = new TextToSpeech(this, this); 330 } else if (mTtsEngineReady) { 331 setTtsLanguage(); 332 } 333 } 334 335 if (mEnableAudio || mEnableVibrate) { 336 ToneType toneType = ToneType.CMAS_DEFAULT; 337 if (intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE) != null) { 338 toneType = (ToneType) intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE); 339 } 340 playAlertTone(toneType); 341 } else { 342 stopSelf(); 343 return START_NOT_STICKY; 344 } 345 346 // Record the initial call state here so that the new alarm has the 347 // newest state. 348 mInitialCallState = mTelephonyManager.getCallState(); 349 350 return START_STICKY; 351 } 352 353 // Volume suggested by media team for in-call alarms. 354 private static final float IN_CALL_VOLUME = 0.125f; 355 356 /** 357 * Start playing the alert sound. 358 * @param toneType the alert tone type (e.g. default, earthquake, tsunami, etc..) 359 */ playAlertTone(ToneType toneType)360 private void playAlertTone(ToneType toneType) { 361 // stop() checks to see if we are already playing. 362 stop(); 363 364 log("playAlertTone: toneType=" + toneType); 365 366 // Vibration duration in milliseconds 367 long vibrateDuration = 0; 368 369 // Start the vibration first. 370 if (mEnableVibrate) { 371 372 int[] patternArray = getApplicationContext().getResources(). 373 getIntArray(R.array.default_vibration_pattern); 374 long[] vibrationPattern = new long[patternArray.length]; 375 376 for (int i = 0; i < patternArray.length; i++) { 377 vibrationPattern[i] = patternArray[i]; 378 vibrateDuration += patternArray[i]; 379 } 380 381 mVibrator.vibrate(vibrationPattern, -1); 382 } 383 384 385 if (mEnableAudio) { 386 // future optimization: reuse media player object 387 mMediaPlayer = new MediaPlayer(); 388 mMediaPlayer.setOnErrorListener(new OnErrorListener() { 389 public boolean onError(MediaPlayer mp, int what, int extra) { 390 loge("Error occurred while playing audio."); 391 mp.stop(); 392 mp.release(); 393 mMediaPlayer = null; 394 return true; 395 } 396 }); 397 398 mMediaPlayer.setOnCompletionListener(new OnCompletionListener() { 399 public void onCompletion(MediaPlayer mp) { 400 if (DBG) log("Audio playback complete."); 401 mHandler.sendMessage(mHandler.obtainMessage(ALERT_SOUND_FINISHED)); 402 return; 403 } 404 }); 405 406 try { 407 // Check if we are in a call. If we are, play the alert 408 // sound at a low volume to not disrupt the call. 409 if (mTelephonyManager.getCallState() 410 != TelephonyManager.CALL_STATE_IDLE) { 411 log("in call: reducing volume"); 412 mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); 413 } 414 415 log("Locale=" + getResources().getConfiguration().getLocales()); 416 417 // Load the tones based on type 418 switch (toneType) { 419 case EARTHQUAKE: 420 setDataSourceFromResource(getResources(), mMediaPlayer, 421 R.raw.etws_earthquake); 422 break; 423 case TSUNAMI: 424 setDataSourceFromResource(getResources(), mMediaPlayer, 425 R.raw.etws_tsunami); 426 break; 427 case OTHER: 428 setDataSourceFromResource(getResources(), mMediaPlayer, 429 R.raw.etws_other_disaster); 430 break; 431 case ETWS_DEFAULT: 432 setDataSourceFromResource(getResources(), mMediaPlayer, 433 R.raw.etws_default); 434 break; 435 case CMAS_DEFAULT: 436 default: 437 setDataSourceFromResource(getResources(), mMediaPlayer, 438 R.raw.cmas_default); 439 } 440 441 // start playing alert audio (unless master volume is vibrate only or silent). 442 mAudioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION, 443 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 444 445 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 446 mMediaPlayer.setLooping(false); 447 mMediaPlayer.prepare(); 448 mMediaPlayer.start(); 449 450 } catch (Exception ex) { 451 loge("Failed to play alert sound: " + ex); 452 // Immediately move into the next state ALERT_SOUND_FINISHED. 453 mHandler.sendMessage(mHandler.obtainMessage(ALERT_SOUND_FINISHED)); 454 } 455 } else { 456 // In normal mode (playing tone + vibration), this service will stop after audio 457 // playback is done. However, if the device is in vibrate only mode, we need to stop 458 // the service right after vibration because there won't be any audio complete callback 459 // to stop the service. Unfortunately it's not like MediaPlayer has onCompletion() 460 // callback that we can use, we'll have to use our own timer to stop the service. 461 mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_SOUND_FINISHED), 462 vibrateDuration); 463 } 464 465 mState = STATE_ALERTING; 466 } 467 setDataSourceFromResource(Resources resources, MediaPlayer player, int res)468 private static void setDataSourceFromResource(Resources resources, 469 MediaPlayer player, int res) throws java.io.IOException { 470 AssetFileDescriptor afd = resources.openRawResourceFd(res); 471 if (afd != null) { 472 player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 473 afd.getLength()); 474 afd.close(); 475 } 476 } 477 478 /** 479 * Stops alert audio and speech. 480 */ stop()481 public void stop() { 482 if (DBG) log("stop()"); 483 484 if (mPlayReminderIntent != null) { 485 mPlayReminderIntent.cancel(); 486 mPlayReminderIntent = null; 487 } 488 489 mHandler.removeMessages(ALERT_SOUND_FINISHED); 490 mHandler.removeMessages(ALERT_PAUSE_FINISHED); 491 492 if (mState == STATE_ALERTING) { 493 // Stop audio playing 494 if (mMediaPlayer != null) { 495 try { 496 mMediaPlayer.stop(); 497 mMediaPlayer.release(); 498 } catch (IllegalStateException e) { 499 // catch "Unable to retrieve AudioTrack pointer for stop()" exception 500 loge("exception trying to stop media player"); 501 } 502 mMediaPlayer = null; 503 } 504 505 // Stop vibrator 506 mVibrator.cancel(); 507 } else if (mState == STATE_SPEAKING && mTts != null) { 508 try { 509 mTts.stop(); 510 } catch (IllegalStateException e) { 511 // catch "Unable to retrieve AudioTrack pointer for stop()" exception 512 loge("exception trying to stop text-to-speech"); 513 } 514 } 515 mState = STATE_IDLE; 516 } 517 log(String msg)518 private static void log(String msg) { 519 Log.d(TAG, msg); 520 } 521 loge(String msg)522 private static void loge(String msg) { 523 Log.e(TAG, msg); 524 } 525 } 526