/**
* Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
* This file is part of CSipSimple.
*
* CSipSimple is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* If you own a pjsip commercial license you can also redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as an android library.
*
* CSipSimple is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSipSimple. If not, see <http://www.gnu.org/licenses/>.
*/
package com.csipsimple.service;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import com.csipsimple.api.MediaState;
import com.csipsimple.api.SipConfigManager;
import com.csipsimple.api.SipManager;
import com.csipsimple.service.SipService.SameThreadException;
import com.csipsimple.service.SipService.SipRunnable;
import com.csipsimple.utils.Compatibility;
import com.csipsimple.utils.Log;
import com.csipsimple.utils.Ringer;
import com.csipsimple.utils.accessibility.AccessibilityWrapper;
import com.csipsimple.utils.audio.AudioFocusWrapper;
import com.csipsimple.utils.bluetooth.BluetoothWrapper;
import com.csipsimple.utils.bluetooth.BluetoothWrapper.BluetoothChangeListener;
@SuppressLint("InlinedApi")
public class MediaManager implements BluetoothChangeListener {
final private static String THIS_FILE = "MediaManager";
private SipService service;
private AudioManager audioManager;
private Ringer ringer;
//Locks
private WifiLock wifiLock;
private WakeLock screenLock;
// Media settings to save / resore
private boolean isSetAudioMode = false;
//By default we assume user want bluetooth.
//If bluetooth is not available connection will never be done and then
//UI will not show bluetooth is activated
private boolean userWantBluetooth = false;
private boolean userWantSpeaker = false;
private boolean userWantMicrophoneMute = false;
private boolean restartAudioWhenRoutingChange = true;
private Intent mediaStateChangedIntent;
//Bluetooth related
private BluetoothWrapper bluetoothWrapper;
private AudioFocusWrapper audioFocusWrapper;
private AccessibilityWrapper accessibilityManager;
private SharedPreferences prefs;
private boolean useSgsWrkAround = false;
private boolean useWebRTCImpl = false;
private boolean doFocusAudio = true;
private boolean startBeforeInit;
private static int modeSipInCall = AudioManager.MODE_NORMAL;
public MediaManager(SipService aService) {
service = aService;
audioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE);
prefs = service.getSharedPreferences("audio", Context.MODE_PRIVATE);
//prefs = PreferenceManager.getDefaultSharedPreferences(service);
accessibilityManager = AccessibilityWrapper.getInstance();
accessibilityManager.init(service);
ringer = new Ringer(service);
mediaStateChangedIntent = new Intent(SipManager.ACTION_SIP_MEDIA_CHANGED);
//Try to reset if there were a crash in a call could restore previous settings
restoreAudioState();
}
public void startService() {
if(bluetoothWrapper == null) {
bluetoothWrapper = BluetoothWrapper.getInstance(service);
bluetoothWrapper.setBluetoothChangeListener(this);
bluetoothWrapper.register();
}
if(audioFocusWrapper == null) {
audioFocusWrapper = AudioFocusWrapper.getInstance();
audioFocusWrapper.init(service, audioManager);
}
modeSipInCall = service.getPrefs().getInCallMode();
useSgsWrkAround = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.USE_SGS_CALL_HACK);
useWebRTCImpl = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.USE_WEBRTC_HACK);
doFocusAudio = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.DO_FOCUS_AUDIO);
userWantBluetooth = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.AUTO_CONNECT_BLUETOOTH);
userWantSpeaker = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.AUTO_CONNECT_SPEAKER);
restartAudioWhenRoutingChange = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.RESTART_AUDIO_ON_ROUTING_CHANGES);
startBeforeInit = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.SETUP_AUDIO_BEFORE_INIT);
}
public void stopService() {
Log.i(THIS_FILE, "Remove media manager....");
if(bluetoothWrapper != null) {
bluetoothWrapper.unregister();
bluetoothWrapper.setBluetoothChangeListener(null);
bluetoothWrapper = null;
}
}
private int getAudioTargetMode() {
int targetMode = modeSipInCall;
if(service.getPrefs().useModeApi()) {
Log.d(THIS_FILE, "User want speaker now..." + userWantSpeaker);
if(!service.getPrefs().generateForSetCall()) {
return userWantSpeaker ? AudioManager.MODE_NORMAL : AudioManager.MODE_IN_CALL;
}else {
return userWantSpeaker ? AudioManager.MODE_IN_CALL: AudioManager.MODE_NORMAL ;
}
}
if(userWantBluetooth) {
targetMode = AudioManager.MODE_NORMAL;
}
Log.d(THIS_FILE, "Target mode... : " + targetMode);
return targetMode;
}
public int validateAudioClockRate(int clockRate) {
if(bluetoothWrapper != null && clockRate != 8000) {
if(userWantBluetooth && bluetoothWrapper.canBluetooth()) {
return -1;
}
}
return 0;
}
public void setAudioInCall(boolean beforeInit) {
if(!beforeInit || (beforeInit && startBeforeInit) ) {
actualSetAudioInCall();
}
}
public void unsetAudioInCall() {
actualUnsetAudioInCall();
}
/**
* Set the audio mode as in call
*/
@SuppressWarnings("deprecation")
private synchronized void actualSetAudioInCall() {
//Ensure not already set
if(isSetAudioMode) {
return;
}
stopRing();
saveAudioState();
// Set the rest of the phone in a better state to not interferate with current call
// Do that only if we were not already in silent mode
/*
* Not needed anymore with on flight gsm call capture
if(audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT) {
audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ON);
audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, AudioManager.VIBRATE_SETTING_OFF);
audioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
}
*/
//LOCKS
//Wifi management if necessary
ContentResolver ctntResolver = service.getContentResolver();
Compatibility.setWifiSleepPolicy(ctntResolver, Compatibility.getWifiSleepPolicyNever());
//Acquire wifi lock
WifiManager wman = (WifiManager) service.getSystemService(Context.WIFI_SERVICE);
if(wifiLock == null) {
wifiLock = wman.createWifiLock(
(Compatibility.isCompatible(9)) ? WifiManager.WIFI_MODE_FULL_HIGH_PERF : WifiManager.WIFI_MODE_FULL,
"com.csipsimple.InCallLock");
wifiLock.setReferenceCounted(false);
}
WifiInfo winfo = wman.getConnectionInfo();
if(winfo != null) {
DetailedState dstate = WifiInfo.getDetailedStateOf(winfo.getSupplicantState());
//We assume that if obtaining ip addr, we are almost connected so can keep wifi lock
if(dstate == DetailedState.OBTAINING_IPADDR || dstate == DetailedState.CONNECTED) {
if(!wifiLock.isHeld()) {
wifiLock.acquire();
}
}
//This wake lock purpose is to prevent PSP wifi mode
if(service.getPrefs().getPreferenceBooleanValue(SipConfigManager.KEEP_AWAKE_IN_CALL)) {
if(screenLock == null) {
PowerManager pm = (PowerManager) service.getSystemService(Context.POWER_SERVICE);
screenLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "com.csipsimple.onIncomingCall.SCREEN");
screenLock.setReferenceCounted(false);
}
//Ensure single lock
if(!screenLock.isHeld()) {
screenLock.acquire();
}
}
}
if(!useWebRTCImpl) {
//Audio routing
int targetMode = getAudioTargetMode();
Log.d(THIS_FILE, "Set mode audio in call to "+targetMode);
if(service.getPrefs().generateForSetCall()) {
boolean needOutOfSilent = (audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT);
if(needOutOfSilent) {
audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
ToneGenerator toneGenerator = new ToneGenerator( AudioManager.STREAM_VOICE_CALL, 1);
toneGenerator.startTone(41 /*ToneGenerator.TONE_CDMA_CONFIRM*/);
toneGenerator.stopTone();
toneGenerator.release();
// Restore silent mode
if(needOutOfSilent) {
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
}
}
//Set mode
if(targetMode != AudioManager.MODE_IN_CALL && useSgsWrkAround) {
//For galaxy S we need to set in call mode before to reset stack
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
audioManager.setMode(targetMode);
//Routing
if(service.getPrefs().useRoutingApi()) {
audioManager.setRouting(targetMode, userWantSpeaker?AudioManager.ROUTE_SPEAKER:AudioManager.ROUTE_EARPIECE, AudioManager.ROUTE_ALL);
}else {
audioManager.setSpeakerphoneOn(userWantSpeaker ? true : false);
}
audioManager.setMicrophoneMute(false);
if(bluetoothWrapper != null && userWantBluetooth && bluetoothWrapper.canBluetooth()) {
Log.d(THIS_FILE, "Try to enable bluetooth");
bluetoothWrapper.setBluetoothOn(true);
}
}else {
//WebRTC implementation for routing
int apiLevel = Compatibility.getApiLevel();
//SetAudioMode
// ***IMPORTANT*** When the API level for honeycomb (H) has been
// decided,
// the condition should be changed to include API level 8 to H-1.
if ( android.os.Build.BRAND.equalsIgnoreCase("Samsung") && (8 == apiLevel)) {
// Set Samsung specific VoIP mode for 2.2 devices
int mode = 4;
audioManager.setMode(mode);
if (audioManager.getMode() != mode) {
Log.e(THIS_FILE, "Could not set audio mode for Samsung device");
}
}
//SetPlayoutSpeaker
if ((3 == apiLevel) || (4 == apiLevel)) {
// 1.5 and 1.6 devices
if (userWantSpeaker) {
// route audio to back speaker
audioManager.setMode(AudioManager.MODE_NORMAL);
} else {
// route audio to earpiece
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
} else {
// 2.x devices
if ((android.os.Build.BRAND.equalsIgnoreCase("samsung")) &&
((5 == apiLevel) || (6 == apiLevel) ||
(7 == apiLevel))) {
// Samsung 2.0, 2.0.1 and 2.1 devices
if (userWantSpeaker) {
// route audio to back speaker
audioManager.setMode(AudioManager.MODE_IN_CALL);
audioManager.setSpeakerphoneOn(userWantSpeaker);
} else {
// route audio to earpiece
audioManager.setSpeakerphoneOn(userWantSpeaker);
audioManager.setMode(AudioManager.MODE_NORMAL);
}
} else {
// Non-Samsung and Samsung 2.2 and up devices
audioManager.setSpeakerphoneOn(userWantSpeaker);
}
}
}
//Set stream solo/volume/focus
int inCallStream = Compatibility.getInCallStream(userWantBluetooth);
if(doFocusAudio) {
if(!accessibilityManager.isEnabled()) {
audioManager.setStreamSolo(inCallStream, true);
}
audioFocusWrapper.focus(userWantBluetooth);
}
Log.d(THIS_FILE, "Initial volume level : " + service.getPrefs().getInitialVolumeLevel());
setStreamVolume(inCallStream,
(int) (audioManager.getStreamMaxVolume(inCallStream) * service.getPrefs().getInitialVolumeLevel()),
0);
isSetAudioMode = true;
// System.gc();
}
/**
* Save current audio mode in order to be able to restore it once done
*/
@SuppressWarnings("deprecation")
private synchronized void saveAudioState() {
if( prefs.getBoolean("isSavedAudioState", false) ) {
//If we have already set, do not set it again !!!
return;
}
ContentResolver ctntResolver = service.getContentResolver();
Editor ed = prefs.edit();
// ed.putInt("savedVibrateRing", audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER));
// ed.putInt("savedVibradeNotif", audioManager.getVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION));
// ed.putInt("savedRingerMode", audioManager.getRingerMode());
ed.putInt("savedWifiPolicy" , Compatibility.getWifiSleepPolicy(ctntResolver));
int inCallStream = Compatibility.getInCallStream(userWantBluetooth);
ed.putInt("savedVolume", audioManager.getStreamVolume(inCallStream));
int targetMode = getAudioTargetMode();
if(service.getPrefs().useRoutingApi()) {
ed.putInt("savedRoute", audioManager.getRouting(targetMode));
}else {
ed.putBoolean("savedSpeakerPhone", audioManager.isSpeakerphoneOn());
}
ed.putInt("savedMode", audioManager.getMode());
ed.putBoolean("isSavedAudioState", true);
ed.commit();
}
/**
* Restore the state of the audio
*/
@SuppressWarnings("deprecation")
private final synchronized void restoreAudioState() {
if( !prefs.getBoolean("isSavedAudioState", false) ) {
//If we have NEVER set, do not try to reset !
return;
}
ContentResolver ctntResolver = service.getContentResolver();
Compatibility.setWifiSleepPolicy(ctntResolver, prefs.getInt("savedWifiPolicy", Compatibility.getWifiSleepPolicyDefault()));
// audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, prefs.getInt("savedVibrateRing", AudioManager.VIBRATE_SETTING_ONLY_SILENT));
// audioManager.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION, prefs.getInt("savedVibradeNotif", AudioManager.VIBRATE_SETTING_OFF));
// audioManager.setRingerMode(prefs.getInt("savedRingerMode", AudioManager.RINGER_MODE_NORMAL));
int inCallStream = Compatibility.getInCallStream(userWantBluetooth);
setStreamVolume(inCallStream, prefs.getInt("savedVolume", (int)(audioManager.getStreamMaxVolume(inCallStream)*0.8) ), 0);
int targetMode = getAudioTargetMode();
if(service.getPrefs().useRoutingApi()) {
audioManager.setRouting(targetMode, prefs.getInt("savedRoute", AudioManager.ROUTE_SPEAKER), AudioManager.ROUTE_ALL);
}else {
audioManager.setSpeakerphoneOn(prefs.getBoolean("savedSpeakerPhone", false));
}
audioManager.setMode(prefs.getInt("savedMode", AudioManager.MODE_NORMAL));
Editor ed = prefs.edit();
ed.putBoolean("isSavedAudioState", false);
ed.commit();
}
/**
* Reset the audio mode
*/
private synchronized void actualUnsetAudioInCall() {
if(!prefs.getBoolean("isSavedAudioState", false) || !isSetAudioMode) {
return;
}
Log.d(THIS_FILE, "Unset Audio In call");
int inCallStream = Compatibility.getInCallStream(userWantBluetooth);
if(bluetoothWrapper != null) {
//This fixes the BT activation but... but... seems to introduce a lot of other issues
//bluetoothWrapper.setBluetoothOn(true);
Log.d(THIS_FILE, "Unset bt");
bluetoothWrapper.setBluetoothOn(false);
}
audioManager.setMicrophoneMute(false);
if(doFocusAudio) {
audioManager.setStreamSolo(inCallStream, false);
audioFocusWrapper.unFocus();
}
restoreAudioState();
if(wifiLock != null && wifiLock.isHeld()) {
wifiLock.release();
}
if(screenLock != null && screenLock.isHeld()) {
Log.d(THIS_FILE, "Release screen lock");
screenLock.release();
}
isSetAudioMode = false;
}
/**
* Start ringing announce for a given contact.
* It will also focus audio for us.
* @param remoteContact the contact to ring for. May resolve the contact ringtone if any.
*/
synchronized public void startRing(String remoteContact) {
saveAudioState();
if(!ringer.isRinging()) {
ringer.ring(remoteContact, service.getPrefs().getRingtone());
}else {
Log.d(THIS_FILE, "Already ringing ....");
}
}
/**
* Stop all ringing. <br/>
* Warning, this will not unfocus audio.
*/
synchronized public void stopRing() {
if(ringer.isRinging()) {
ringer.stopRing();
}
}
/**
* Stop call announcement.
*/
public void stopRingAndUnfocus() {
stopRing();
audioFocusWrapper.unFocus();
}
public void resetSettings() {
userWantBluetooth = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.AUTO_CONNECT_BLUETOOTH);
userWantSpeaker = service.getPrefs().getPreferenceBooleanValue(SipConfigManager.AUTO_CONNECT_SPEAKER);
userWantMicrophoneMute = false;
}
public void toggleMute() throws SameThreadException {
setMicrophoneMute(!userWantMicrophoneMute);
}
public void setMicrophoneMute(boolean on) {
if(on != userWantMicrophoneMute ) {
userWantMicrophoneMute = on;
setSoftwareVolume();
broadcastMediaChanged();
}
}
public void setSpeakerphoneOn(boolean on) throws SameThreadException {
if(service != null && restartAudioWhenRoutingChange && !ringer.isRinging()) {
service.setNoSnd();
userWantSpeaker = on;
service.setSnd();
}else {
userWantSpeaker = on;
audioManager.setSpeakerphoneOn(on);
}
broadcastMediaChanged();
}
public void setBluetoothOn(boolean on) throws SameThreadException {
Log.d(THIS_FILE, "Set BT "+on);
if(service != null && restartAudioWhenRoutingChange && !ringer.isRinging()) {
service.setNoSnd();
userWantBluetooth = on;
service.setSnd();
}else {
userWantBluetooth = on;
bluetoothWrapper.setBluetoothOn(on);
}
broadcastMediaChanged();
}
public MediaState getMediaState() {
MediaState mediaState = new MediaState();
// Micro
mediaState.isMicrophoneMute = userWantMicrophoneMute;
mediaState.canMicrophoneMute = true; /*&& !mediaState.isBluetoothScoOn*/ //Compatibility.isCompatible(5);
// Speaker
mediaState.isSpeakerphoneOn = userWantSpeaker;
mediaState.canSpeakerphoneOn = true && !mediaState.isBluetoothScoOn; //Compatibility.isCompatible(5);
//Bluetooth
if(bluetoothWrapper != null) {
mediaState.isBluetoothScoOn = bluetoothWrapper.isBluetoothOn();
mediaState.canBluetoothSco = bluetoothWrapper.canBluetooth();
}else {
mediaState.isBluetoothScoOn = false;
mediaState.canBluetoothSco = false;
}
return mediaState;
}
/**
* Change the audio volume amplification according to the fact we are using bluetooth
*/
public void setSoftwareVolume(){
if(service != null) {
final boolean useBT = (bluetoothWrapper != null && bluetoothWrapper.isBluetoothOn());
String speaker_key = useBT ? SipConfigManager.SND_BT_SPEAKER_LEVEL : SipConfigManager.SND_SPEAKER_LEVEL;
String mic_key = useBT ? SipConfigManager.SND_BT_MIC_LEVEL : SipConfigManager.SND_MIC_LEVEL;
final float speakVolume = service.getPrefs().getPreferenceFloatValue(speaker_key);
final float micVolume = userWantMicrophoneMute? 0 : service.getPrefs().getPreferenceFloatValue(mic_key);
service.getExecutor().execute(new SipRunnable() {
@Override
protected void doRun() throws SameThreadException {
service.confAdjustTxLevel(speakVolume);
service.confAdjustRxLevel(micVolume);
// Force the BT mode to normal
if(useBT) {
audioManager.setMode(AudioManager.MODE_NORMAL);
}
}
});
}
}
public void broadcastMediaChanged() {
service.sendBroadcast(mediaStateChangedIntent, SipManager.PERMISSION_USE_SIP);
}
private static final String ACTION_AUDIO_VOLUME_UPDATE = "org.openintents.audio.action_volume_update";
private static final String EXTRA_STREAM_TYPE = "org.openintents.audio.extra_stream_type";
private static final String EXTRA_VOLUME_INDEX = "org.openintents.audio.extra_volume_index";
private static final String EXTRA_RINGER_MODE = "org.openintents.audio.extra_ringer_mode";
private static final int EXTRA_VALUE_UNKNOWN = -9999;
private void broadcastVolumeWillBeUpdated(int streamType, int index) {
Intent notificationIntent = new Intent(ACTION_AUDIO_VOLUME_UPDATE);
notificationIntent.putExtra(EXTRA_STREAM_TYPE, streamType);
notificationIntent.putExtra(EXTRA_VOLUME_INDEX, index);
notificationIntent.putExtra(EXTRA_RINGER_MODE, EXTRA_VALUE_UNKNOWN);
service.sendBroadcast(notificationIntent, null);
}
public void setStreamVolume(int streamType, int index, int flags) {
broadcastVolumeWillBeUpdated(streamType, index);
audioManager.setStreamVolume(streamType, index, flags);
}
public void adjustStreamVolume(int streamType, int direction, int flags) {
broadcastVolumeWillBeUpdated(streamType, EXTRA_VALUE_UNKNOWN);
audioManager.adjustStreamVolume(streamType, direction, flags);
if(streamType == AudioManager.STREAM_RING) {
// Update ringer
ringer.updateRingerMode();
}
int inCallStream = Compatibility.getInCallStream(userWantBluetooth);
if(streamType == inCallStream) {
int maxLevel = audioManager.getStreamMaxVolume(inCallStream);
float modifiedLevel = (audioManager.getStreamVolume(inCallStream)/(float) maxLevel)*10.0f;
// Update default stream level
service.getPrefs().setPreferenceFloatValue(SipConfigManager.SND_STREAM_LEVEL, modifiedLevel);
}
}
// Public accessor
public boolean doesUserWantMicrophoneMute() {
return userWantMicrophoneMute;
}
public boolean doesUserWantBluetooth() {
return userWantBluetooth;
}
// The possible tones we can play.
public static final int TONE_NONE = 0;
public static final int TONE_CALL_WAITING = 1;
public static final int TONE_BUSY = 2;
public static final int TONE_CONGESTION = 3;
public static final int TONE_BATTERY_LOW = 4;
public static final int TONE_CALL_ENDED = 5;
/**
* Play a tone in band
* @param toneId the id of the tone to play.
*/
public void playInCallTone(int toneId) {
(new InCallTonePlayer(toneId)).start();
}
/**
* Helper class to play tones through the earpiece (or speaker / BT)
* during a call, using the ToneGenerator.
*
* To use, just instantiate a new InCallTonePlayer
* (passing in the TONE_* constant for the tone you want)
* and start() it.
*
* When we're done playing the tone, if the phone is idle at that
* point, we'll reset the audio routing and speaker state.
* (That means that for tones that get played *after* a call
* disconnects, like "busy" or "congestion" or "call ended", you
* should NOT call resetAudioStateAfterDisconnect() yourself.
* Instead, just start the InCallTonePlayer, which will automatically
* defer the resetAudioStateAfterDisconnect() call until the tone
* finishes playing.)
*/
private class InCallTonePlayer extends Thread {
private int mToneId;
// The tone volume relative to other sounds in the stream
private static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
private static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
InCallTonePlayer(int toneId) {
super();
mToneId = toneId;
}
@Override
public void run() {
Log.d(THIS_FILE, "InCallTonePlayer.run(toneId = " + mToneId + ")...");
int toneType; // passed to ToneGenerator.startTone()
int toneVolume; // passed to the ToneGenerator constructor
int toneLengthMillis;
switch (mToneId) {
case TONE_CALL_WAITING:
toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 5000;
break;
case TONE_BUSY:
toneType = ToneGenerator.TONE_SUP_BUSY;
toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
break;
case TONE_CONGESTION:
toneType = ToneGenerator.TONE_SUP_CONGESTION;
toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 4000;
break;
case TONE_BATTERY_LOW:
// For now, use ToneGenerator.TONE_PROP_ACK (two quick
// beeps). TODO: is there some other ToneGenerator
// tone that would be more appropriate here? Or
// should we consider adding a new custom tone?
toneType = ToneGenerator.TONE_PROP_ACK;
toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
toneLengthMillis = 1000;
break;
case TONE_CALL_ENDED:
toneType = ToneGenerator.TONE_PROP_PROMPT;
toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
toneLengthMillis = 2000;
break;
default:
throw new IllegalArgumentException("Bad toneId: " + mToneId);
}
// If the mToneGenerator creation fails, just continue without it. It is
// a local audio signal, and is not as important.
ToneGenerator toneGenerator;
try {
toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, toneVolume);
// if (DBG) log("- created toneGenerator: " + toneGenerator);
} catch (RuntimeException e) {
Log.w(THIS_FILE, "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
toneGenerator = null;
}
// Using the ToneGenerator (with the CALL_WAITING / BUSY /
// CONGESTION tones at least), the ToneGenerator itself knows
// the right pattern of tones to play; we do NOT need to
// manually start/stop each individual tone, or manually
// insert the correct delay between tones. (We just start it
// and let it run for however long we want the tone pattern to
// continue.)
//
// TODO: When we stop the ToneGenerator in the middle of a
// "tone pattern", it sounds bad if we cut if off while the
// tone is actually playing. Consider adding API to the
// ToneGenerator to say "stop at the next silent part of the
// pattern", or simply "play the pattern N times and then
// stop."
if (toneGenerator != null) {
toneGenerator.startTone(toneType);
SystemClock.sleep(toneLengthMillis);
toneGenerator.stopTone();
Log.v(THIS_FILE, "- InCallTonePlayer: done playing.");
toneGenerator.release();
}
}
}
@Override
public void onBluetoothStateChanged(int status) {
setSoftwareVolume();
broadcastMediaChanged();
}
/**
* @param defaultRate
* @return
*/
public long getBestSampleRate(long defaultRate) {
if (Compatibility.isCompatible(android.os.Build.VERSION_CODES.JELLY_BEAN_MR1)) {
String sampleRateString = audioFocusWrapper.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
return (sampleRateString == null ?
defaultRate : Integer.parseInt(sampleRateString));
} else {
return defaultRate;
}
}
}