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