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