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.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