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