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