Home | History | Annotate | Download | only in cellbroadcastreceiver
      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      */
    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      */
    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      */
    179     public void onUtteranceCompleted(String utteranceId) {
    180         stopSelf();
    181     }
    182 
    183     @Override
    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
    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
    209     public IBinder onBind(Intent intent) {
    210         return null;
    211     }
    212 
    213     @Override
    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      */
    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.
    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 
    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      */
    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