• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.PendingIntent;
20 import android.app.Service;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.AssetFileDescriptor;
24 import android.content.res.Resources;
25 import android.media.AudioManager;
26 import android.media.MediaPlayer;
27 import android.media.MediaPlayer.OnErrorListener;
28 import android.media.Ringtone;
29 import android.media.RingtoneManager;
30 import android.net.Uri;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.Message;
34 import android.os.Vibrator;
35 import android.speech.tts.TextToSpeech;
36 import android.telephony.PhoneStateListener;
37 import android.telephony.TelephonyManager;
38 import android.util.Log;
39 
40 import java.util.Locale;
41 
42 import 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  */
48 public 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
onInit(int status)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      */
setTtsLanguage()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
onUtteranceCompleted(String utteranceId)198     public void onUtteranceCompleted(String utteranceId) {
199         stopSelf();
200     }
201 
202     @Override
onCreate()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
onDestroy()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
onBind(Intent intent)233     public IBinder onBind(Intent intent) {
234         return null;
235     }
236 
237     @Override
onStartCommand(Intent intent, int flags, int startId)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      */
play(int duration)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.
startAlarm(MediaPlayer player)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 
setDataSourceFromResource(Resources resources, MediaPlayer player, int res)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 
playAlertReminderSound()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      */
stop()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 
log(String msg)430     private static void log(String msg) {
431         Log.d(TAG, msg);
432     }
433 
loge(String msg)434     private static void loge(String msg) {
435         Log.e(TAG, msg);
436     }
437 }
438