/* LinphoneManager.java Copyright (C) 2010 Belledonne Communications, Grenoble, France This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.linphone; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Resources; import android.hardware.SensorEvent; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Vibrator; import android.preference.PreferenceManager; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import net.chrislehmann.linphone.R; import org.linphone.core.Hacks; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAuthInfo; import org.linphone.core.LinphoneCall; import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneChatRoom; import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCore.EcCalibratorStatus; import org.linphone.core.LinphoneCore.FirewallPolicy; import org.linphone.core.LinphoneCore.GlobalState; import org.linphone.core.LinphoneCore.RegistrationState; import org.linphone.core.LinphoneCore.Transports; import org.linphone.core.LinphoneCoreException; import org.linphone.core.LinphoneCoreFactory; import org.linphone.core.LinphoneCoreListener; import org.linphone.core.LinphoneFriend; import org.linphone.core.LinphoneProxyConfig; import org.linphone.core.Log; import org.linphone.core.PayloadType; import org.linphone.core.Version; import org.linphone.core.video.AndroidCameraRecordManager; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Timer; import java.util.TimerTask; import static android.media.AudioManager.MODE_IN_CALL; import static android.media.AudioManager.MODE_NORMAL; import static android.media.AudioManager.MODE_RINGTONE; import static android.media.AudioManager.ROUTE_EARPIECE; import static android.media.AudioManager.ROUTE_SPEAKER; import static android.media.AudioManager.STREAM_RING; import static android.media.AudioManager.STREAM_VOICE_CALL; import static android.media.AudioManager.VIBRATE_TYPE_RINGER; import static net.chrislehmann.linphone.R.string.pref_codec_amr_key; import static net.chrislehmann.linphone.R.string.pref_codec_ilbc_key; import static net.chrislehmann.linphone.R.string.pref_codec_speex16_key; import static net.chrislehmann.linphone.R.string.pref_codec_speex32_key; import static net.chrislehmann.linphone.R.string.pref_echo_cancellation_key; import static net.chrislehmann.linphone.R.string.pref_video_enable_key; import static org.linphone.core.LinphoneCall.State.CallEnd; import static org.linphone.core.LinphoneCall.State.Error; import static org.linphone.core.LinphoneCall.State.IncomingReceived; /** * * Manager of the low level LibLinphone stuff.<br /> * Including:<ul> * <li>Starting C liblinphone</li> * <li>Reacting to C liblinphone state changes</li> * <li>Calling Linphone android service listener methods</li> * <li>Interacting from Android GUI/service with low level SIP stuff/</li> * </ul> * * Add Service Listener to react to Linphone state changes. * * @author Guillaume Beraudo * */ public final class LinphoneManager implements LinphoneCoreListener { private static LinphoneManager instance; private AudioManager mAudioManager; private PowerManager mPowerManager; private SharedPreferences mPref; private Resources mR; private LinphoneCore mLc; private static Transports initialTransports; private static LinphonePreferenceManager lpm; private String lastLcStatusMessage; private String basePath; private static boolean sExited; private LinphoneManager(final Context c) { sExited=false; basePath = c.getFilesDir().getAbsolutePath(); linphoneInitialConfigFile = basePath + "/linphonerc"; linphoneConfigFile = basePath + "/.linphonerc"; linphoneRootCaFile = basePath + "/rootca.pem"; ringSoundFile = basePath + "/oldphone_mono.wav"; ringbackSoundFile = basePath + "/ringback.wav"; lpm = LinphonePreferenceManager.getInstance(c); mAudioManager = ((AudioManager) c.getSystemService(Context.AUDIO_SERVICE)); mVibrator = (Vibrator) c.getSystemService(Context.VIBRATOR_SERVICE); mPref = PreferenceManager.getDefaultSharedPreferences(c); mPowerManager = (PowerManager) c.getSystemService(Context.POWER_SERVICE); mR = c.getResources(); } private static final int LINPHONE_VOLUME_STREAM = STREAM_VOICE_CALL; private static final int dbStep = 4; /** Called when the activity is first created. */ private final String linphoneInitialConfigFile; private final String linphoneRootCaFile; private final String linphoneConfigFile; private final String ringSoundFile; private final String ringbackSoundFile; private Timer mTimer = new Timer("Linphone scheduler"); private BroadcastReceiver mKeepAliveReceiver = new KeepAliveReceiver(); private native void hackSpeakerState(boolean speakerOn); @SuppressWarnings("unused") private static void sRouteAudioToSpeakerHelperHelper(boolean speakerOn) { getInstance().routeAudioToSpeakerHelperHelper(speakerOn); } private void routeAudioToSpeakerHelperHelper(boolean speakerOn) { if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack()) setAudioModeIncallForGalaxyS(); if (lpm.useSpecificAudioModeHack() != -1) mAudioManager.setMode(lpm.useSpecificAudioModeHack()); if (Hacks.needRoutingAPI() || lpm.useAudioRoutingAPIHack()) { mAudioManager.setRouting( MODE_NORMAL, speakerOn? ROUTE_SPEAKER : ROUTE_EARPIECE, AudioManager.ROUTE_ALL); } else { mAudioManager.setSpeakerphoneOn(speakerOn); } } private synchronized void routeAudioToSpeakerHelper(boolean speakerOn) { final LinphoneCall call = mLc.getCurrentCall(); if (call != null && call.getState() == State.StreamsRunning && Hacks.needPausingCallForSpeakers()) { Log.d("Hack to have speaker=", speakerOn, " while on call"); hackSpeakerState(speakerOn); } else { routeAudioToSpeakerHelperHelper(speakerOn); } } public void routeAudioToSpeaker() { routeAudioToSpeakerHelper(true); if (mLc.isIncall()) { /*disable EC*/ mLc.getCurrentCall().enableEchoCancellation(false); mLc.getCurrentCall().enableEchoLimiter(true); } } public void routeAudioToReceiver() { routeAudioToSpeakerHelper(false); if (mLc.isIncall()) { //Restore default value mLc.getCurrentCall().enableEchoCancellation(mLc.isEchoCancellationEnabled()); mLc.getCurrentCall().enableEchoLimiter(false); } } public synchronized static final LinphoneManager createAndStart( Context c, LinphoneServiceListener listener) { if (instance != null) throw new RuntimeException("Linphone Manager is already initialized"); instance = new LinphoneManager(c); instance.serviceListener = listener; instance.startLibLinphone(c); if (Version.isVideoCapable()) { AndroidCameraRecordManager.getInstance().startOrientationSensor(c.getApplicationContext()); } return instance; } public static synchronized final LinphoneManager getInstance() { if (instance != null) return instance; if (sExited) { throw new RuntimeException("Linphone Manager was already destroyed. " + "Better use getLcIfManagerNotDestroyed and check returned value"); } throw new RuntimeException("Linphone Manager should be created before accessed"); } public static synchronized final LinphoneCore getLc() { return getInstance().mLc; } public boolean isSpeakerOn() { return (Integer.parseInt(Build.VERSION.SDK) <=4 && mAudioManager.getRouting(MODE_NORMAL) == ROUTE_SPEAKER) || Integer.parseInt(Build.VERSION.SDK) >4 &&mAudioManager.isSpeakerphoneOn(); } public void newOutgoingCall(AddressType address) { String to = address.getText().toString(); if (mLc.isIncall()) { serviceListener.tryingNewOutgoingCallButAlreadyInCall(); return; } LinphoneAddress lAddress; try { lAddress = mLc.interpretUrl(to); } catch (LinphoneCoreException e) { serviceListener.tryingNewOutgoingCallButWrongDestinationAddress(); return; } lAddress.setDisplayName(address.getDisplayedName()); try { if (Version.isVideoCapable()) { boolean prefVideoEnable = isVideoEnabled(); int key = R.string.pref_video_initiate_call_with_video_key; boolean prefInitiateWithVideo = mPref.getBoolean(mR.getString(key), false); resetCameraFromPreferences(); CallManager.getInstance().inviteAddress(lAddress, prefVideoEnable && prefInitiateWithVideo); } else { CallManager.getInstance().inviteAddress(lAddress, false); } } catch (LinphoneCoreException e) { serviceListener.tryingNewOutgoingCallButCannotGetCallParameters(); return; } } public void resetCameraFromPreferences() { boolean useFrontCam = mPref.getBoolean(mR.getString(R.string.pref_video_use_front_camera_key), false); AndroidCameraRecordManager.getInstance().setUseFrontCamera(useFrontCam); } public static interface AddressType { void setText(CharSequence s); CharSequence getText(); void setDisplayedName(String s); String getDisplayedName(); } public static interface NewOutgoingCallUiListener { public void onWrongDestinationAddress(); public void onCannotGetCallParameters(); public void onAlreadyInCall(); } public void sendStaticImage(boolean send) { if (mLc.isIncall()) { mLc.getCurrentCall().enableCamera(!send); } } public void playDtmf(ContentResolver r, char dtmf) { try { if (Settings.System.getInt(r, Settings.System.DTMF_TONE_WHEN_DIALING) == 0) { // audible touch disabled: don't play on speaker, only send in outgoing stream return; } } catch (SettingNotFoundException e) {} getLc().playDtmf(dtmf, -1); } public void changeResolution() { BandwidthManager manager = BandwidthManager.getInstance(); manager.setUserRestriction(!manager.isUserRestriction()); } public void terminateCall() { if (mLc.isIncall()) { mLc.terminateCall(mLc.getCurrentCall()); } } /** * Camera will be restarted when mediastreamer chain is recreated and setParameters is called. */ public void switchCamera() { AndroidCameraRecordManager.getInstance().stopVideoRecording(); AndroidCameraRecordManager.getInstance().toggleUseFrontCamera(); CallManager.getInstance().updateCall(); } public void toggleCameraMuting() { AndroidCameraRecordManager rm = AndroidCameraRecordManager.getInstance(); sendStaticImage(rm.toggleMute()); } private synchronized void startLibLinphone(final Context context) { try { copyAssetsFromPackage(context); mLc = LinphoneCoreFactory.instance().createLinphoneCore( this, linphoneConfigFile, linphoneInitialConfigFile, null); mLc.enableIpv6(mPref.getBoolean(getString(R.string.pref_ipv6_key), false)); mLc.setZrtpSecretsCache(basePath+"/zrtp_secrets"); mLc.setPlaybackGain(3); mLc.setRing(null); mLc.setRootCA(linphoneRootCaFile); try { initFromConf(context); } catch (LinphoneException e) { Log.w("no config ready yet"); } TimerTask lTask = new TimerTask() { @Override public void run() { mLc.iterate(); } }; mTimer.scheduleAtFixedRate(lTask, 0, 100); IntentFilter lFilter = new IntentFilter(Intent.ACTION_SCREEN_ON); lFilter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(mKeepAliveReceiver, lFilter); } catch (Exception e) { Log.e(e, "Cannot start linphone"); } } private void copyAssetsFromPackage(Context context) throws IOException { copyIfNotExist(context, R.raw.oldphone_mono,ringSoundFile); copyIfNotExist(context, R.raw.ringback,ringbackSoundFile); copyFromPackage(context, R.raw.linphonerc, new File(linphoneInitialConfigFile).getName()); copyIfNotExist(context, R.raw.rootca, new File(linphoneRootCaFile).getName()); } private void copyIfNotExist(Context context, int ressourceId,String target) throws IOException { File lFileToCopy = new File(target); if (!lFileToCopy.exists()) { copyFromPackage(context, ressourceId,lFileToCopy.getName()); } } private void copyFromPackage(Context context, int ressourceId,String target) throws IOException{ FileOutputStream lOutputStream = context.openFileOutput (target, 0); InputStream lInputStream = mR.openRawResource(ressourceId); int readByte; byte[] buff = new byte[8048]; while (( readByte = lInputStream.read(buff))!=-1) { lOutputStream.write(buff,0, readByte); } lOutputStream.flush(); lOutputStream.close(); lInputStream.close(); } public boolean detectVideoCodec(String mime) { for (PayloadType videoCodec : mLc.getVideoCodecs()) { if (mime.equals(videoCodec.getMime())) return true; } return false; } public boolean detectAudioCodec(String mime){ for (PayloadType audioCodec : mLc.getAudioCodecs()) { if (mime.equals(audioCodec.getMime())) return true; } return false; } public void initFromConf(Context context) throws LinphoneConfigException { //traces boolean lIsDebug = mPref.getBoolean(getString(R.string.pref_debug_key), false); LinphoneCoreFactory.instance().setDebugMode(lIsDebug); if (initialTransports == null) initialTransports = mLc.getSignalingTransportPorts(); setSignalingTransportsFromConfiguration(initialTransports); try { // Configure audio codecs // enableDisableAudioCodec("speex", 32000, R.string.pref_codec_speex32_key); enableDisableAudioCodec("speex", 32000, false); enableDisableAudioCodec("speex", 16000, R.string.pref_codec_speex16_key); enableDisableAudioCodec("speex", 8000, R.string.pref_codec_speex8_key); enableDisableAudioCodec("iLBC", 8000, R.string.pref_codec_ilbc_key); enableDisableAudioCodec("GSM", 8000, R.string.pref_codec_gsm_key); enableDisableAudioCodec("PCMU", 8000, R.string.pref_codec_pcmu_key); enableDisableAudioCodec("PCMA", 8000, R.string.pref_codec_pcma_key); enableDisableAudioCodec("AMR", 8000, R.string.pref_codec_amr_key); // Configure video codecs for (PayloadType videoCodec : mLc.getVideoCodecs()) { enableDisableVideoCodecs(videoCodec); } mLc.enableEchoCancellation(mPref.getBoolean(getString(R.string.pref_echo_cancellation_key),false)); } catch (LinphoneCoreException e) { throw new LinphoneConfigException(getString(R.string.wrong_settings),e); } boolean isVideoEnabled = isVideoEnabled(); mLc.enableVideo(isVideoEnabled, isVideoEnabled); //1 read proxy config from preferences String lUserName = mPref.getString(getString(R.string.pref_username_key), null); if (lUserName == null || lUserName.length()==0) { throw new LinphoneConfigException(getString(R.string.wrong_username)); } String lPasswd = mPref.getString(getString(R.string.pref_passwd_key), null); if (lPasswd == null || lPasswd.length()==0) { throw new LinphoneConfigException(getString(R.string.wrong_passwd)); } String lDomain = mPref.getString(getString(R.string.pref_domain_key), null); if (lDomain == null || lDomain.length()==0) { throw new LinphoneConfigException(getString(R.string.wrong_domain)); } String lStun = mPref.getString(getString(R.string.pref_stun_server_key), null); //stun server mLc.setStunServer(lStun); mLc.setFirewallPolicy((lStun!=null && lStun.length()>0) ? FirewallPolicy.UseStun : FirewallPolicy.NoFirewall); //auth mLc.clearAuthInfos(); LinphoneAuthInfo lAuthInfo = LinphoneCoreFactory.instance().createAuthInfo(lUserName, lPasswd,null); mLc.addAuthInfo(lAuthInfo); //proxy mLc.clearProxyConfigs(); String lProxy = mPref.getString(getString(R.string.pref_proxy_key),null); if (lProxy == null || lProxy.length() == 0) { lProxy = "sip:"+lDomain; } if (!lProxy.startsWith("sip:")) { lProxy = "sip:"+lProxy; } //get Default proxy if any LinphoneProxyConfig lDefaultProxyConfig = mLc.getDefaultProxyConfig(); String lIdentity = "sip:"+lUserName+"@"+lDomain; try { if (lDefaultProxyConfig == null) { lDefaultProxyConfig = LinphoneCoreFactory.instance().createProxyConfig(lIdentity, lProxy, null,true); mLc.addProxyConfig(lDefaultProxyConfig); mLc.setDefaultProxyConfig(lDefaultProxyConfig); } else { lDefaultProxyConfig.edit(); lDefaultProxyConfig.setIdentity(lIdentity); lDefaultProxyConfig.setProxy(lProxy); lDefaultProxyConfig.enableRegister(true); lDefaultProxyConfig.done(); } lDefaultProxyConfig = mLc.getDefaultProxyConfig(); if (lDefaultProxyConfig !=null) { //prefix String lPrefix = mPref.getString(getString(R.string.pref_prefix_key), null); if (lPrefix != null) { lDefaultProxyConfig.setDialPrefix(lPrefix); } //escape + lDefaultProxyConfig.setDialEscapePlus(mPref.getBoolean(getString(R.string.pref_escape_plus_key),false)); //outbound proxy if (mPref.getBoolean(getString(R.string.pref_enable_outbound_proxy_key), false)) { lDefaultProxyConfig.setRoute(lProxy); } else { lDefaultProxyConfig.setRoute(null); } } //init network state ConnectivityManager lConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo lInfo = lConnectivityManager.getActiveNetworkInfo(); mLc.setNetworkReachable( lInfo !=null? lConnectivityManager.getActiveNetworkInfo().getState() ==NetworkInfo.State.CONNECTED:false); } catch (LinphoneCoreException e) { throw new LinphoneConfigException(getString(R.string.wrong_settings),e); } } private boolean getBool(int key, boolean defValue) { return mPref.getBoolean(getString(key), defValue); } private void setSignalingTransportsFromConfiguration(Transports t) { Transports ports = new Transports(t); boolean useStandardPort = getBool(R.string.pref_transport_use_standard_ports_key, false); int lPreviousPort = ports.tcp +ports.udp +ports.tls; // assume only one port is active if (!getBool(R.string.pref_transport_tcp_key, false)) { ports.tcp = 0; } else if (useStandardPort) { ports.tcp = 5060; } else if (ports.tcp==0){ ports.tcp=lPreviousPort; } if (!getBool(R.string.pref_transport_udp_key, false)) { ports.udp = 0; } else if (useStandardPort) { ports.udp = 5060; } else if (ports.udp==0) { ports.udp = lPreviousPort; } if (!getBool(R.string.pref_transport_tls_key, false)) { ports.tls = 0; } else if (useStandardPort) { ports.tls = 5061; } else if (ports.tls==0) { ports.tls=lPreviousPort; } mLc.setSignalingTransportPorts(ports); } private void enableDisableAudioCodec(String codec, int rate, int key) throws LinphoneCoreException { PayloadType pt = mLc.findPayloadType(codec, rate); if (pt !=null) { boolean enable= mPref.getBoolean(getString(key),false); mLc.enablePayloadType(pt, enable); } } private void enableDisableAudioCodec(String codec, int rate, boolean enable) throws LinphoneCoreException { PayloadType pt = mLc.findPayloadType(codec, rate); if (pt !=null) { mLc.enablePayloadType(pt, enable); } } private void enableDisableVideoCodecs(PayloadType videoCodec) throws LinphoneCoreException { String mime = videoCodec.getMime(); int key; if ("MP4V-ES".equals(mime)) { key = R.string.pref_video_codec_mpeg4_key; } else if ("H264".equals(mime)) { key = R.string.pref_video_codec_h264_key; } else if ("H263-1998".equals(mime)) { key = R.string.pref_video_codec_h263_key; } else if ("VP8-DRAFT-0-3-2".equals(mime)) { key = R.string.pref_video_codec_vp8_key; } else { Log.e("Unhandled video codec ", mime); mLc.enablePayloadType(videoCodec, false); return; } boolean enable= mPref.getBoolean(getString(key),false); mLc.enablePayloadType(videoCodec, enable); } public static synchronized void destroy(Context context) { if (instance == null) return; sExited=true; try { instance.mTimer.cancel(); instance.mLc.destroy(); context.unregisterReceiver(instance.mKeepAliveReceiver); } finally { instance.mLc = null; instance = null; } } private String getString(int key) { return mR.getString(key); } public interface LinphoneServiceListener { void onGlobalStateChanged(GlobalState state, String message); void tryingNewOutgoingCallButCannotGetCallParameters(); void tryingNewOutgoingCallButWrongDestinationAddress(); void tryingNewOutgoingCallButAlreadyInCall(); void onRegistrationStateChanged(RegistrationState state, String message); void onCallStateChanged(LinphoneCall call, State state, String message); void onRingerPlayerCreated(MediaPlayer mRingerPlayer); void onDisplayStatus(String message); void onAlreadyInVideoCall(); void onCallEncryptionChanged(LinphoneCall call, boolean encrypted, String authenticationToken); } public interface EcCalibrationListener { void onEcCalibrationStatus(EcCalibratorStatus status, int delayMs); } private LinphoneServiceListener serviceListener; private LinphoneCall.State mCurrentCallState; private MediaPlayer mRingerPlayer; private Vibrator mVibrator; public void displayWarning(LinphoneCore lc, String message) {} public void authInfoRequested(LinphoneCore lc, String realm, String username) {} public void byeReceived(LinphoneCore lc, String from) {} public void displayMessage(LinphoneCore lc, String message) {} public void show(LinphoneCore lc) {} public void newSubscriptionRequest(LinphoneCore lc,LinphoneFriend lf,String url) {} public void notifyPresenceReceived(LinphoneCore lc, LinphoneFriend lf) {} public void textReceived(LinphoneCore lc, LinphoneChatRoom cr, LinphoneAddress from, String message) {} public String getLastLcStatusMessage() { return lastLcStatusMessage; } public void displayStatus(final LinphoneCore lc, final String message) { Log.i(message); lastLcStatusMessage=message; serviceListener.onDisplayStatus(message); } public void globalState(final LinphoneCore lc, final LinphoneCore.GlobalState state, final String message) { Log.i("new state [", state, "]"); serviceListener.onGlobalStateChanged(state, message); } public void registrationState(final LinphoneCore lc, final LinphoneProxyConfig cfg,final LinphoneCore.RegistrationState state,final String message) { Log.i("new state [" + state + "]"); serviceListener.onRegistrationStateChanged(state, message); } public void callState(final LinphoneCore lc,final LinphoneCall call, final State state, final String message) { Log.i("new state [", state, "]"); if (state == IncomingReceived && !call.equals(lc.getCurrentCall())) { if (call.getReplacedCall()==null){ //no multicall support, just decline lc.terminateCall(call); }//otherwise it will be accepted automatically. return; } if (state == IncomingReceived) { // Brighten screen for at least 10 seconds WakeLock wl = mPowerManager.newWakeLock( PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE |PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "incoming_call"); wl.acquire(10000); startRinging(); } if (mCurrentCallState == IncomingReceived) { //previous state was ringing, so stop ringing stopRinging(); routeAudioToReceiver(); } if (state == CallEnd || state == Error) { mAudioManager.setMode(MODE_NORMAL); } if (state == State.Connected) { if (Hacks.needSoftvolume() || LinphonePreferenceManager.getInstance().useSoftvolume()) { adjustSoftwareVolume(0); // Synchronize } } mCurrentCallState=state; serviceListener.onCallStateChanged(call, state, message); } public void callEncryptionChanged(LinphoneCore lc, LinphoneCall call, boolean encrypted, String authenticationToken) { serviceListener.onCallEncryptionChanged(call, encrypted, authenticationToken); } public void ecCalibrationStatus(final LinphoneCore lc,final EcCalibratorStatus status, final int delayMs, final Object data) { EcCalibrationListener listener = (EcCalibrationListener) data; listener.onEcCalibrationStatus(status, delayMs); } public void startEcCalibration(EcCalibrationListener l) throws LinphoneCoreException { int oldVolume = mAudioManager.getStreamVolume(STREAM_VOICE_CALL); int maxVolume = mAudioManager.getStreamMaxVolume(STREAM_VOICE_CALL); mAudioManager.setStreamVolume(STREAM_VOICE_CALL, maxVolume, 0); mLc.startEchoCalibration(l); mAudioManager.setStreamVolume(STREAM_VOICE_CALL, oldVolume, 0); } private synchronized void startRinging() { if (Hacks.needGalaxySAudioHack()) { mAudioManager.setMode(MODE_RINGTONE); } try { if (mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER) && mVibrator !=null) { long[] patern = {0,1000,1000}; mVibrator.vibrate(patern, 1); } if (mRingerPlayer == null) { mRingerPlayer = new MediaPlayer(); mRingerPlayer.setAudioStreamType(STREAM_RING); serviceListener.onRingerPlayerCreated(mRingerPlayer); mRingerPlayer.prepare(); mRingerPlayer.setLooping(true); mRingerPlayer.start(); } else { Log.w("already ringing"); } } catch (Exception e) { Log.e(e, "cannot handle incoming call"); } } private synchronized void stopRinging() { if (mRingerPlayer !=null) { mRingerPlayer.stop(); mRingerPlayer.release(); mRingerPlayer=null; } if (mVibrator!=null) { mVibrator.cancel(); } // You may need to call galaxys audio hack after this method } public String extractADisplayName() { final LinphoneAddress remote = mLc.getRemoteAddress(); if (remote == null) return mR.getString(R.string.unknown_incoming_call_name); final String displayName = remote.getDisplayName(); if (displayName!=null) { return displayName; } else if (remote.getUserName() != null){ return remote.getUserName(); } else { String rms = remote.toString(); if (rms != null && rms.length() > 1) return rms; return mR.getString(R.string.unknown_incoming_call_name); } } public static boolean reinviteWithVideo() { return CallManager.getInstance().reinviteWithVideo(); } public boolean isVideoEnabled() { return mPref.getBoolean(getString(R.string.pref_video_enable_key), false); } public void setAudioModeIncallForGalaxyS() { stopRinging(); mAudioManager.setMode(MODE_IN_CALL); } // Called on first launch only public void initializePayloads() { Log.i("Initializing supported payloads"); Editor e = mPref.edit(); boolean fastCpu = Version.isArmv7(); e.putBoolean(getString(pref_echo_cancellation_key), fastCpu); e.putBoolean(getString(R.string.pref_codec_gsm_key), true); e.putBoolean(getString(R.string.pref_codec_pcma_key), true); e.putBoolean(getString(R.string.pref_codec_pcmu_key), true); e.putBoolean(getString(R.string.pref_codec_speex8_key), true); e.putBoolean(getString(pref_codec_speex16_key), fastCpu); e.putBoolean(getString(pref_codec_speex32_key), fastCpu); boolean ilbc = LinphoneService.isReady() && LinphoneManager.getLc() .findPayloadType("iLBC", 8000)!=null; e.putBoolean(getString(pref_codec_ilbc_key), ilbc); boolean amr = LinphoneService.isReady() && LinphoneManager.getLc() .findPayloadType("AMR", 8000)!=null; e.putBoolean(getString(pref_codec_amr_key), amr); if (Version.sdkStrictlyBelow(5) || !Version.hasNeon() || !Hacks.hasCamera()) { e.putBoolean(getString(pref_video_enable_key), false); } e.commit(); } public void addVideo() { if (!LinphoneManager.getLc().isIncall()) return; if (!reinviteWithVideo()) { serviceListener.onAlreadyInVideoCall(); } } public boolean acceptCallIfIncomingPending() throws LinphoneCoreException { if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack()) setAudioModeIncallForGalaxyS(); if (mLc.isInComingInvitePending()) { mLc.acceptCall(mLc.getCurrentCall()); return true; } return false; } public String extractIncomingRemoteName() { if (!mR.getBoolean(R.bool.show_full_remote_address_on_incoming_call)) return extractADisplayName(); LinphoneAddress remote = mLc.getRemoteAddress(); if (remote != null) return remote.toString(); return mR.getString(R.string.unknown_incoming_call_name); } public void adjustSoftwareVolume(int i) { int oldVolume = mAudioManager.getStreamVolume(LINPHONE_VOLUME_STREAM); int maxVolume = mAudioManager.getStreamMaxVolume(LINPHONE_VOLUME_STREAM); int nextVolume = oldVolume +i; if (nextVolume > maxVolume) nextVolume = maxVolume; if (nextVolume < 0) nextVolume = 0; mLc.adjustSoftwareVolume((nextVolume - maxVolume)* dbStep); } public static Boolean isProximitySensorNearby(final SensorEvent event) { float threshold = 4.001f; // <= 4 cm is near final float distanceInCm = event.values[0]; final float maxDistance = event.sensor.getMaximumRange(); Log.d("Proximity sensor report [", distanceInCm, "] , for max range [", maxDistance, "]"); if (maxDistance <= threshold) { // Case binary 0/1 and short sensors threshold = maxDistance; } return distanceInCm < threshold; } public static synchronized LinphoneCore getLcIfManagerNotDestroyedOrNull() { if (sExited) { // Can occur if the UI thread play a posted event but in the meantime the LinphoneManager was destroyed // Ex: stop call and quickly terminate application. Log.w("Trying to get linphone core while LinphoneManager already destroyed"); return null; } return getLc(); } }