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