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