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