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