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