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.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
    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      */
    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
    198     public void onUtteranceCompleted(String utteranceId) {
    199         stopSelf();
    200     }
    201 
    202     @Override
    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
    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
    233     public IBinder onBind(Intent intent) {
    234         return null;
    235     }
    236 
    237     @Override
    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      */
    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.
    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 
    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 
    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      */
    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 
    430     private static void log(String msg) {
    431         Log.d(TAG, msg);
    432     }
    433 
    434     private static void loge(String msg) {
    435         Log.e(TAG, msg);
    436     }
    437 }
    438