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 static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
     20 import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.VDBG;
     21 
     22 import android.app.Service;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.res.AssetFileDescriptor;
     26 import android.content.res.Resources;
     27 import android.media.AudioAttributes;
     28 import android.media.AudioDeviceInfo;
     29 import android.media.AudioManager;
     30 import android.media.MediaPlayer;
     31 import android.media.MediaPlayer.OnCompletionListener;
     32 import android.media.MediaPlayer.OnErrorListener;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.IBinder;
     36 import android.os.Message;
     37 import android.os.Vibrator;
     38 import android.preference.PreferenceManager;
     39 import android.provider.Settings;
     40 import android.speech.tts.TextToSpeech;
     41 import android.telephony.PhoneStateListener;
     42 import android.telephony.TelephonyManager;
     43 import android.util.Log;
     44 
     45 import com.android.cellbroadcastreceiver.CellBroadcastAlertService.AlertType;
     46 
     47 import java.util.Locale;
     48 import java.util.MissingResourceException;
     49 
     50 /**
     51  * Manages alert audio and vibration and text-to-speech. Runs as a service so that
     52  * it can continue to play if another activity overrides the CellBroadcastListActivity.
     53  */
     54 public class CellBroadcastAlertAudio extends Service implements TextToSpeech.OnInitListener,
     55         TextToSpeech.OnUtteranceCompletedListener {
     56     private static final String TAG = "CellBroadcastAlertAudio";
     57 
     58     /** Action to start playing alert audio/vibration/speech. */
     59     static final String ACTION_START_ALERT_AUDIO = "ACTION_START_ALERT_AUDIO";
     60 
     61     /** Extra for message body to speak (if speech enabled in settings). */
     62     public static final String ALERT_AUDIO_MESSAGE_BODY =
     63             "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_BODY";
     64 
     65     /** Extra for text-to-speech preferred language (if speech enabled in settings). */
     66     public static final String ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE =
     67             "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE";
     68 
     69     /** Extra for text-to-speech default language when preferred language is
     70         not available (if speech enabled in settings). */
     71     public static final String ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE =
     72             "com.android.cellbroadcastreceiver.ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE";
     73 
     74     /** Extra for alert tone type */
     75     public static final String ALERT_AUDIO_TONE_TYPE =
     76             "com.android.cellbroadcastreceiver.ALERT_AUDIO_TONE_TYPE";
     77 
     78     /** Extra for alert audio vibration enabled (from settings). */
     79     public static final String ALERT_AUDIO_VIBRATE_EXTRA =
     80             "com.android.cellbroadcastreceiver.ALERT_AUDIO_VIBRATE";
     81 
     82     /** Extra for alert vibration pattern (unless master volume is silent). */
     83     public static final String ALERT_AUDIO_VIBRATION_PATTERN_EXTRA =
     84             "com.android.cellbroadcastreceiver.ALERT_VIBRATION_PATTERN";
     85 
     86     private static final String TTS_UTTERANCE_ID = "com.android.cellbroadcastreceiver.UTTERANCE_ID";
     87 
     88     /** Pause duration between alert sound and alert speech. */
     89     private static final int PAUSE_DURATION_BEFORE_SPEAKING_MSEC = 1000;
     90 
     91     private static final int STATE_IDLE = 0;
     92     private static final int STATE_ALERTING = 1;
     93     private static final int STATE_PAUSING = 2;
     94     private static final int STATE_SPEAKING = 3;
     95 
     96     private int mState;
     97 
     98     private TextToSpeech mTts;
     99     private boolean mTtsEngineReady;
    100 
    101     private String mMessageBody;
    102     private String mMessagePreferredLanguage;
    103     private String mMessageDefaultLanguage;
    104     private boolean mTtsLanguageSupported;
    105     private boolean mEnableVibrate;
    106     private boolean mEnableAudio;
    107     private boolean mUseFullVolume;
    108     private boolean mResetAlarmVolumeNeeded;
    109     private int mUserSetAlarmVolume;
    110     private int[] mVibrationPattern;
    111 
    112     private Vibrator mVibrator;
    113     private MediaPlayer mMediaPlayer;
    114     private AudioManager mAudioManager;
    115     private TelephonyManager mTelephonyManager;
    116     private int mInitialCallState;
    117 
    118     // Internal messages
    119     private static final int ALERT_SOUND_FINISHED = 1000;
    120     private static final int ALERT_PAUSE_FINISHED = 1001;
    121     private final Handler mHandler = new Handler() {
    122         @Override
    123         public void handleMessage(Message msg) {
    124             switch (msg.what) {
    125                 case ALERT_SOUND_FINISHED:
    126                     if (DBG) log("ALERT_SOUND_FINISHED");
    127                     stop();     // stop alert sound
    128                     // if we can speak the message text
    129                     if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
    130                         mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED),
    131                                 PAUSE_DURATION_BEFORE_SPEAKING_MSEC);
    132                         mState = STATE_PAUSING;
    133                     } else {
    134                         if (DBG) log("MessageEmpty = " + (mMessageBody == null) +
    135                                 ", mTtsEngineReady = " + mTtsEngineReady +
    136                                 ", mTtsLanguageSupported = " + mTtsLanguageSupported);
    137                         stopSelf();
    138                         mState = STATE_IDLE;
    139                     }
    140                     // Set alert reminder depending on user preference
    141                     CellBroadcastAlertReminder.queueAlertReminder(getApplicationContext(), true);
    142                     break;
    143 
    144                 case ALERT_PAUSE_FINISHED:
    145                     if (DBG) log("ALERT_PAUSE_FINISHED");
    146                     int res = TextToSpeech.ERROR;
    147                     if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
    148                         if (DBG) log("Speaking broadcast text: " + mMessageBody);
    149 
    150                         Bundle params = new Bundle();
    151                         // Play TTS in the alarm stream, which we use for playing alert tones as
    152                         // well.
    153                         params.putInt(TextToSpeech.Engine.KEY_PARAM_STREAM,
    154                                 AudioManager.STREAM_ALARM);
    155                         // Use the non-public parameter 2 --> TextToSpeech.QUEUE_DESTROY for TTS.
    156                         // The entire playback queue is purged. This is different from QUEUE_FLUSH
    157                         // in that all entries are purged, not just entries from a given caller.
    158                         // This is for emergency so we want to kill all other TTS sessions.
    159                         res = mTts.speak(mMessageBody, 2, params, TTS_UTTERANCE_ID);
    160                         mState = STATE_SPEAKING;
    161                     }
    162                     if (res != TextToSpeech.SUCCESS) {
    163                         loge("TTS engine not ready or language not supported or speak() failed");
    164                         stopSelf();
    165                         mState = STATE_IDLE;
    166                     }
    167                     break;
    168 
    169                 default:
    170                     loge("Handler received unknown message, what=" + msg.what);
    171             }
    172         }
    173     };
    174 
    175     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    176         @Override
    177         public void onCallStateChanged(int state, String ignored) {
    178             // Stop the alert sound and speech if the call state changes.
    179             if (state != TelephonyManager.CALL_STATE_IDLE
    180                     && state != mInitialCallState) {
    181                 stopSelf();
    182             }
    183         }
    184     };
    185 
    186     /**
    187      * Callback from TTS engine after initialization.
    188      * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
    189      */
    190     @Override
    191     public void onInit(int status) {
    192         if (VDBG) log("onInit() TTS engine status: " + status);
    193         if (status == TextToSpeech.SUCCESS) {
    194             mTtsEngineReady = true;
    195             mTts.setOnUtteranceCompletedListener(this);
    196             // try to set the TTS language to match the broadcast
    197             setTtsLanguage();
    198         } else {
    199             mTtsEngineReady = false;
    200             mTts = null;
    201             loge("onInit() TTS engine error: " + status);
    202         }
    203     }
    204 
    205     /**
    206      * Try to set the TTS engine language to the preferred language. If failed, set
    207      * it to the default language. mTtsLanguageSupported will be updated based on the response.
    208      */
    209     private void setTtsLanguage() {
    210 
    211         String language = mMessagePreferredLanguage;
    212         if (language == null || language.isEmpty() ||
    213                 TextToSpeech.LANG_AVAILABLE != mTts.isLanguageAvailable(new Locale(language))) {
    214             language = mMessageDefaultLanguage;
    215             if (language == null || language.isEmpty() ||
    216                     TextToSpeech.LANG_AVAILABLE != mTts.isLanguageAvailable(new Locale(language))) {
    217                 mTtsLanguageSupported = false;
    218                 return;
    219             }
    220             if (DBG) log("Language '" + mMessagePreferredLanguage + "' is not available, using" +
    221                     "the default language '" + mMessageDefaultLanguage + "'");
    222         }
    223 
    224         if (DBG) log("Setting TTS language to '" + language + '\'');
    225 
    226         try {
    227             int result = mTts.setLanguage(new Locale(language));
    228             if (DBG) log("TTS setLanguage() returned: " + result);
    229             mTtsLanguageSupported = (result == TextToSpeech.LANG_AVAILABLE);
    230         }
    231         catch (MissingResourceException e) {
    232             mTtsLanguageSupported = false;
    233             loge("Language '" + language + "' is not available.");
    234         }
    235     }
    236 
    237     /**
    238      * Callback from TTS engine.
    239      * @param utteranceId the identifier of the utterance.
    240      */
    241     @Override
    242     public void onUtteranceCompleted(String utteranceId) {
    243         if (utteranceId.equals(TTS_UTTERANCE_ID)) {
    244             // When we reach here, it could be TTS completed or TTS was cut due to another
    245             // new alert started playing. We don't want to stop the service in the later case.
    246             if (mState == STATE_SPEAKING) {
    247                 stopSelf();
    248             }
    249         }
    250     }
    251 
    252     @Override
    253     public void onCreate() {
    254         mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    255         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    256         // Listen for incoming calls to kill the alarm.
    257         mTelephonyManager =
    258                 (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    259         mTelephonyManager.listen(
    260                 mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    261     }
    262 
    263     @Override
    264     public void onDestroy() {
    265         // stop audio, vibration and TTS
    266         stop();
    267         // Stop listening for incoming calls.
    268         mTelephonyManager.listen(mPhoneStateListener, 0);
    269         // shutdown TTS engine
    270         if (mTts != null) {
    271             try {
    272                 mTts.shutdown();
    273             } catch (IllegalStateException e) {
    274                 // catch "Unable to retrieve AudioTrack pointer for stop()" exception
    275                 loge("exception trying to shutdown text-to-speech");
    276             }
    277         }
    278         if (mEnableAudio) {
    279             // Release the audio focus so other audio (e.g. music) can resume.
    280             // Do not do this in stop() because stop() is also called when we stop the tone (before
    281             // TTS is playing). We only want to release the focus when tone and TTS are played.
    282             mAudioManager.abandonAudioFocus(null);
    283         }
    284         // release the screen bright wakelock acquired by CellBroadcastAlertService
    285         CellBroadcastAlertWakeLock.releaseScreenBrightWakeLock();
    286     }
    287 
    288     @Override
    289     public IBinder onBind(Intent intent) {
    290         return null;
    291     }
    292 
    293     @Override
    294     public int onStartCommand(Intent intent, int flags, int startId) {
    295         // No intent, tell the system not to restart us.
    296         if (intent == null) {
    297             stopSelf();
    298             return START_NOT_STICKY;
    299         }
    300 
    301         // Get text to speak (if enabled by user)
    302         mMessageBody = intent.getStringExtra(ALERT_AUDIO_MESSAGE_BODY);
    303         mMessagePreferredLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_PREFERRED_LANGUAGE);
    304         mMessageDefaultLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_DEFAULT_LANGUAGE);
    305 
    306         // Get config of whether to always sound CBS alerts at full volume.
    307         mUseFullVolume = PreferenceManager.getDefaultSharedPreferences(this)
    308                 .getBoolean(CellBroadcastSettings.KEY_USE_FULL_VOLUME, false);
    309 
    310         // retrieve the vibrate settings from cellbroadcast receiver settings.
    311         mEnableVibrate = intent.getBooleanExtra(ALERT_AUDIO_VIBRATE_EXTRA, true);
    312         // retrieve the vibration patterns
    313         mVibrationPattern = intent.getIntArrayExtra(ALERT_AUDIO_VIBRATION_PATTERN_EXTRA);
    314 
    315         switch (mAudioManager.getRingerMode()) {
    316             case AudioManager.RINGER_MODE_SILENT:
    317                 if (DBG) log("Ringer mode: silent");
    318                 mEnableAudio = false;
    319                 mEnableVibrate = false;
    320                 break;
    321             case AudioManager.RINGER_MODE_VIBRATE:
    322                 if (DBG) log("Ringer mode: vibrate");
    323                 mEnableAudio = false;
    324                 break;
    325             case AudioManager.RINGER_MODE_NORMAL:
    326             default:
    327                 if (DBG) log("Ringer mode: normal");
    328                 mEnableAudio = true;
    329                 break;
    330         }
    331 
    332         if (mUseFullVolume) {
    333             mEnableAudio = true;
    334         }
    335 
    336         if (mMessageBody != null && mEnableAudio) {
    337             if (mTts == null) {
    338                 mTts = new TextToSpeech(this, this);
    339             } else if (mTtsEngineReady) {
    340                 setTtsLanguage();
    341             }
    342         }
    343 
    344         if (mEnableAudio || mEnableVibrate) {
    345             AlertType alertType = AlertType.DEFAULT;
    346             if (intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE) != null) {
    347                 alertType = (AlertType) intent.getSerializableExtra(ALERT_AUDIO_TONE_TYPE);
    348             }
    349             playAlertTone(alertType, mVibrationPattern);
    350         } else {
    351             stopSelf();
    352             return START_NOT_STICKY;
    353         }
    354 
    355         // Record the initial call state here so that the new alarm has the
    356         // newest state.
    357         mInitialCallState = mTelephonyManager.getCallState();
    358 
    359         return START_STICKY;
    360     }
    361 
    362     // Volume suggested by media team for in-call alarms.
    363     private static final float IN_CALL_VOLUME = 0.125f;
    364 
    365     /**
    366      * Start playing the alert sound.
    367      * @param alertType the alert type (e.g. default, earthquake, tsunami, etc..)
    368      * @param patternArray the alert vibration pattern
    369      */
    370     private void playAlertTone(AlertType alertType, int[] patternArray) {
    371         // stop() checks to see if we are already playing.
    372         stop();
    373 
    374         log("playAlertTone: alertType=" + alertType);
    375 
    376         // Vibration duration in milliseconds
    377         long vibrateDuration = 0;
    378 
    379         int customAlertDuration = getResources().getInteger(R.integer.alert_duration);
    380 
    381         // Start the vibration first.
    382         if (mEnableVibrate) {
    383             long[] vibrationPattern = new long[patternArray.length];
    384 
    385             for (int i = 0; i < patternArray.length; i++) {
    386                 vibrationPattern[i] = patternArray[i];
    387                 vibrateDuration += patternArray[i];
    388             }
    389             mVibrator.vibrate(vibrationPattern, 0);
    390         }
    391 
    392 
    393         if (mEnableAudio) {
    394             // future optimization: reuse media player object
    395             mMediaPlayer = new MediaPlayer();
    396             mMediaPlayer.setOnErrorListener(new OnErrorListener() {
    397                 public boolean onError(MediaPlayer mp, int what, int extra) {
    398                     loge("Error occurred while playing audio.");
    399                     mHandler.sendMessage(mHandler.obtainMessage(ALERT_SOUND_FINISHED));
    400                     return true;
    401                 }
    402             });
    403 
    404             // If the duration is specified by the config, use the specified duration. Otherwise,
    405             // just play the alert tone with the tone's duration.
    406             if (customAlertDuration >= 0) {
    407                 mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_SOUND_FINISHED),
    408                         customAlertDuration);
    409             } else {
    410                 mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
    411                     public void onCompletion(MediaPlayer mp) {
    412                         if (DBG) log("Audio playback complete.");
    413                         mHandler.sendMessage(mHandler.obtainMessage(ALERT_SOUND_FINISHED));
    414                         return;
    415                     }
    416                 });
    417             }
    418 
    419             try {
    420                 log("Locale=" + getResources().getConfiguration().getLocales()
    421                         + ", alertType=" + alertType);
    422 
    423                 // Load the tones based on type
    424                 switch (alertType) {
    425                     case ETWS_EARTHQUAKE:
    426                         setDataSourceFromResource(getResources(), mMediaPlayer,
    427                                 R.raw.etws_earthquake);
    428                         break;
    429                     case ETWS_TSUNAMI:
    430                         setDataSourceFromResource(getResources(), mMediaPlayer,
    431                                 R.raw.etws_tsunami);
    432                         break;
    433                     case OTHER:
    434                         setDataSourceFromResource(getResources(), mMediaPlayer,
    435                                 R.raw.etws_other_disaster);
    436                         break;
    437                     case ETWS_DEFAULT:
    438                         setDataSourceFromResource(getResources(), mMediaPlayer,
    439                                 R.raw.etws_default);
    440                         break;
    441                     case INFO:
    442                         // for non-emergency alerts, we are using system default notification sound.
    443                         String sound = Settings.System.getString(
    444                                 getApplicationContext().getContentResolver(),
    445                                 Settings.System.NOTIFICATION_SOUND);
    446                         mMediaPlayer.setDataSource(sound);
    447                         break;
    448                     case TEST:
    449                     case DEFAULT:
    450                     default:
    451                         setDataSourceFromResource(getResources(), mMediaPlayer,
    452                                 R.raw.default_tone);
    453                 }
    454 
    455                 // Request audio focus (though we're going to play even if we don't get it)
    456                 mAudioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM,
    457                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    458                 mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
    459 
    460                 setAlertAudioAttributes();
    461                 setAlertVolume();
    462 
    463                 // If we are using the custom alert duration, set looping to true so we can repeat
    464                 // the alert. The tone playing will stop when ALERT_SOUND_FINISHED arrives.
    465                 // Otherwise we just play the alert tone once.
    466                 mMediaPlayer.setLooping(customAlertDuration >= 0);
    467                 mMediaPlayer.prepare();
    468                 mMediaPlayer.start();
    469 
    470             } catch (Exception ex) {
    471                 loge("Failed to play alert sound: " + ex);
    472                 // Immediately move into the next state ALERT_SOUND_FINISHED.
    473                 mHandler.sendMessage(mHandler.obtainMessage(ALERT_SOUND_FINISHED));
    474             }
    475         } else {
    476             // In normal mode (playing tone + vibration), this service will stop after audio
    477             // playback is done. However, if the device is in vibrate only mode, we need to stop
    478             // the service right after vibration because there won't be any audio complete callback
    479             // to stop the service. Unfortunately it's not like MediaPlayer has onCompletion()
    480             // callback that we can use, we'll have to use our own timer to stop the service.
    481             mHandler.sendMessageDelayed(mHandler.obtainMessage(ALERT_SOUND_FINISHED),
    482                     customAlertDuration >= 0 ? customAlertDuration : vibrateDuration);
    483         }
    484 
    485         mState = STATE_ALERTING;
    486     }
    487 
    488     private static void setDataSourceFromResource(Resources resources,
    489             MediaPlayer player, int res) throws java.io.IOException {
    490         AssetFileDescriptor afd = resources.openRawResourceFd(res);
    491         if (afd != null) {
    492             player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
    493                     afd.getLength());
    494             afd.close();
    495         }
    496     }
    497 
    498     /**
    499      * Stops alert audio and speech.
    500      */
    501     public void stop() {
    502         if (DBG) log("stop()");
    503 
    504         mHandler.removeMessages(ALERT_SOUND_FINISHED);
    505         mHandler.removeMessages(ALERT_PAUSE_FINISHED);
    506 
    507         resetAlarmStreamVolume();
    508 
    509         if (mState == STATE_ALERTING) {
    510             // Stop audio playing
    511             if (mMediaPlayer != null) {
    512                 try {
    513                     mMediaPlayer.stop();
    514                     mMediaPlayer.release();
    515                 } catch (IllegalStateException e) {
    516                     // catch "Unable to retrieve AudioTrack pointer for stop()" exception
    517                     loge("exception trying to stop media player");
    518                 }
    519                 mMediaPlayer = null;
    520             }
    521 
    522             // Stop vibrator
    523             mVibrator.cancel();
    524         } else if (mState == STATE_SPEAKING && mTts != null) {
    525             try {
    526                 mTts.stop();
    527             } catch (IllegalStateException e) {
    528                 // catch "Unable to retrieve AudioTrack pointer for stop()" exception
    529                 loge("exception trying to stop text-to-speech");
    530             }
    531         }
    532         mState = STATE_IDLE;
    533     }
    534 
    535     /**
    536      * Set AudioAttributes for mMediaPlayer. Replacement of deprecated
    537      * mMediaPlayer.setAudioStreamType.
    538      */
    539     private void setAlertAudioAttributes() {
    540         AudioAttributes.Builder builder = new AudioAttributes.Builder();
    541 
    542         builder.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION);
    543         builder.setUsage(AudioAttributes.USAGE_ALARM);
    544         if (mUseFullVolume) {
    545             // Set FLAG_BYPASS_INTERRUPTION_POLICY and FLAG_BYPASS_MUTE so that it enables
    546             // audio in any DnD mode, even in total silence DnD mode (requires MODIFY_PHONE_STATE).
    547             builder.setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
    548                     | AudioAttributes.FLAG_BYPASS_MUTE);
    549         }
    550 
    551         mMediaPlayer.setAudioAttributes(builder.build());
    552     }
    553 
    554     /**
    555      * Set volume for alerts.
    556      */
    557     private void setAlertVolume() {
    558         if (mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE
    559                 || isOnEarphone()) {
    560             // If we are in a call, play the alert
    561             // sound at a low volume to not disrupt the call.
    562             log("in call: reducing volume");
    563             mMediaPlayer.setVolume(IN_CALL_VOLUME);
    564         } else if (mUseFullVolume) {
    565             // If use_full_volume is configured,
    566             // we overwrite volume setting of STREAM_ALARM to full, play at
    567             // max possible volume, and reset it after it's finished.
    568             setAlarmStreamVolumeToFull();
    569         }
    570     }
    571 
    572     private boolean isOnEarphone() {
    573         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
    574 
    575         for (AudioDeviceInfo devInfo : deviceList) {
    576             int type = devInfo.getType();
    577             if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET
    578                     || type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
    579                     || type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
    580                     || type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) {
    581                 return true;
    582             }
    583         }
    584 
    585         return false;
    586     }
    587 
    588     /**
    589      * Set volume of STREAM_ALARM to full.
    590      */
    591     private void setAlarmStreamVolumeToFull() {
    592         log("setting alarm volume to full for cell broadcast alerts.");
    593         mUserSetAlarmVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
    594         mResetAlarmVolumeNeeded = true;
    595         mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM,
    596                 mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM),
    597                 0);
    598     }
    599 
    600     /**
    601      * Reset volume of STREAM_ALARM, if needed.
    602      */
    603     private void resetAlarmStreamVolume() {
    604         if (mResetAlarmVolumeNeeded) {
    605             log("resetting alarm volume to back to " + mUserSetAlarmVolume);
    606             mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mUserSetAlarmVolume, 0);
    607             mResetAlarmVolumeNeeded = false;
    608         }
    609     }
    610 
    611     private static void log(String msg) {
    612         Log.d(TAG, msg);
    613     }
    614 
    615     private static void loge(String msg) {
    616         Log.e(TAG, msg);
    617     }
    618 }
    619