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