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