diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8c9384124..5a734ffae 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,7 +1,7 @@ + android:versionCode="1220" android:versionName="1.2.2" android:installLocation="auto"> @@ -16,19 +16,6 @@ - - - - - - - - - @@ -44,6 +31,20 @@ + + + + + + + + + + + diff --git a/res/layout/conference_details_layout.xml b/res/layout/conference_details_layout.xml index 7290b364f..044b7cb78 100644 --- a/res/layout/conference_details_layout.xml +++ b/res/layout/conference_details_layout.xml @@ -26,8 +26,13 @@ ui:unchecked="@drawable/incall_micro" ui:bgdrawables="true" /> - - + + Registered to %s Fails to register to %s Linphone %s SIP (rfc 3261) compatible phone under GNU Public License V2\n http://www.linphone.org\n\nInstructions\nhttp://www.linphone.org/m/help\n\n© 2011 Belledonne Communications + + + @string/tunnel_mode_entry_value_disabled \ No newline at end of file diff --git a/res/values/non_localizable_strings.xml b/res/values/non_localizable_strings.xml index a5ef23b8b..0bf0c07b6 100644 --- a/res/values/non_localizable_strings.xml +++ b/res/values/non_localizable_strings.xml @@ -1,5 +1,21 @@ + + + pref_tunnel_key + pref_tunnel_mode_key + disabled + 3G_only + always + + @string/tunnel_mode_entry_value_disabled + @string/tunnel_mode_entry_value_3G_only + @string/tunnel_mode_entry_value_always + @string/tunnel_mode_entry_value_auto + + pref_tunnel_host_key + pref_tunnel_port_key + auto pref_audio_use_specific_mode_key pref_audio_hacks_use_galaxys_hack_key diff --git a/res/values/strings.xml b/res/values/strings.xml index 3f320de0e..a0f30fdc0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,6 +1,19 @@ + Tunnel + Hostname + Port + Mode + + + + disabled + 3G only + always + auto + + Audio call ongoing Paused call ongoing Video capturing call ongoing diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 26279ff3f..9f9ec184f 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -21,7 +21,20 @@ + + + + + + + diff --git a/src/org/linphone/AbstractCalleesActivity.java b/src/org/linphone/AbstractCalleesActivity.java index 0147ba002..3b40cb296 100644 --- a/src/org/linphone/AbstractCalleesActivity.java +++ b/src/org/linphone/AbstractCalleesActivity.java @@ -18,6 +18,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.linphone; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -27,9 +28,9 @@ import org.linphone.LinphoneSimpleListener.LinphoneOnAudioChangedListener; import org.linphone.LinphoneSimpleListener.LinphoneOnCallStateChangedListener; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneCall; +import org.linphone.core.LinphoneCall.State; import org.linphone.core.LinphoneCore; import org.linphone.core.Log; -import org.linphone.core.LinphoneCall.State; import android.app.ListActivity; import android.net.Uri; @@ -64,6 +65,8 @@ public abstract class AbstractCalleesActivity extends ListActivity implements Li private Set mChronometers = new HashSet(); + private Handler callqualityHandler; + private List viewsToUpdateCallQuality; @Override /** * Called by the child classes AFTER their own onCreate. @@ -210,6 +213,34 @@ public abstract class AbstractCalleesActivity extends ListActivity implements Li timer.setBase(SystemClock.elapsedRealtime() - 1000 * callDuration); timer.start(); } + + protected final void initCallQualityListener() { + callqualityHandler = new Handler(); + viewsToUpdateCallQuality = new ArrayList(); + callqualityHandler.postDelayed(new Runnable() { + public void run() { + if (viewsToUpdateCallQuality == null) { + return; + } + + for (View v : viewsToUpdateCallQuality) { + LinphoneCall call = (LinphoneCall) v.getTag(); + float newQuality = call.getCurrentQuality(); + updateQualityOfSignalIcon(v, newQuality); + } + + callqualityHandler.postDelayed(this, 1000); + } + },1000); + } + + protected final void registerCallQualityListener(final View v, final LinphoneCall call) { + if (viewsToUpdateCallQuality == null && callqualityHandler == null) { + initCallQualityListener(); + } + v.setTag(call); + viewsToUpdateCallQuality.add(v); + } } @Override @@ -248,4 +279,29 @@ public abstract class AbstractCalleesActivity extends ListActivity implements Li } }); } + + void updateQualityOfSignalIcon(View v, float quality) + { + ImageView qos = (ImageView) v.findViewById(R.id.QoS); + if (quality >= 4) // Good Quality + { + qos.setImageDrawable(getResources().getDrawable(R.drawable.stat_sys_signal_4)); + } + else if (quality >= 3) // Average quality + { + qos.setImageDrawable(getResources().getDrawable(R.drawable.stat_sys_signal_3)); + } + else if (quality >= 2) // Low quality + { + qos.setImageDrawable(getResources().getDrawable(R.drawable.stat_sys_signal_2)); + } + else if (quality >= 1) // Very low quality + { + qos.setImageDrawable(getResources().getDrawable(R.drawable.stat_sys_signal_1)); + } + else // Worst quality + { + qos.setImageDrawable(getResources().getDrawable(R.drawable.stat_sys_signal_0)); + } + } } \ No newline at end of file diff --git a/src/org/linphone/ConferenceDetailsActivity.java b/src/org/linphone/ConferenceDetailsActivity.java index 6555315e0..08bcea633 100644 --- a/src/org/linphone/ConferenceDetailsActivity.java +++ b/src/org/linphone/ConferenceDetailsActivity.java @@ -51,6 +51,13 @@ public class ConferenceDetailsActivity extends AbstractCalleesActivity { return; } setContentView(R.layout.conference_details_layout); + View v=findViewById(R.id.incallHang); + v.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + lc().terminateConference(); + } + }); super.onCreate(savedInstanceState); } @@ -94,6 +101,8 @@ public class ConferenceDetailsActivity extends AbstractCalleesActivity { setCalleePicture(pictureView, address); registerCallDurationTimer(v, call); + registerCallQualityListener(v, call); + return v; } diff --git a/src/org/linphone/DialerActivity.java b/src/org/linphone/DialerActivity.java index b7bf79ff3..97df4a686 100644 --- a/src/org/linphone/DialerActivity.java +++ b/src/org/linphone/DialerActivity.java @@ -31,7 +31,6 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.view.KeyEvent; import android.widget.TextView; import android.widget.Toast; @@ -51,15 +50,16 @@ import android.widget.Toast; public class DialerActivity extends Activity implements LinphoneGuiListener { private TextView mStatus; - private Handler mHandler; private AddressText mAddress; private CallButton mCall; private static DialerActivity instance; + private boolean mPreventDoubleCallOnRotation; private static final String CURRENT_ADDRESS = "org.linphone.current-address"; private static final String CURRENT_DISPLAYNAME = "org.linphone.current-displayname"; + private static final String PREVENT_DOUBLE_CALL = "prevent_call_on_phone_rotation"; /** * @return null if not ready yet @@ -71,8 +71,6 @@ public class DialerActivity extends Activity implements LinphoneGuiListener { public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.dialer); - mHandler = new Handler(); - mAddress = (AddressText) findViewById(R.id.SipUri); ((EraseButton) findViewById(R.id.Erase)).setAddressWidget(mAddress); @@ -89,8 +87,14 @@ public class DialerActivity extends Activity implements LinphoneGuiListener { // call to super must be done after all fields are initialized // because it may call this.enterIncallMode super.onCreate(savedInstanceState); - - checkIfOutgoingCallIntentReceived(); + + mPreventDoubleCallOnRotation=savedInstanceState != null + && savedInstanceState.getBoolean(PREVENT_DOUBLE_CALL, false); + if (mPreventDoubleCallOnRotation) { + Log.i("Prevent launching a new call on rotation"); + } else { + checkIfOutgoingCallIntentReceived(); + } instance = this; super.onCreate(savedInstanceState); @@ -129,6 +133,7 @@ public class DialerActivity extends Activity implements LinphoneGuiListener { savedInstanceState.putCharSequence(CURRENT_ADDRESS, mAddress.getText()); if (mAddress.getDisplayedName() != null) savedInstanceState.putString(CURRENT_DISPLAYNAME,mAddress.getDisplayedName()); + savedInstanceState.putBoolean(PREVENT_DOUBLE_CALL, mPreventDoubleCallOnRotation); } @Override @@ -153,14 +158,23 @@ public class DialerActivity extends Activity implements LinphoneGuiListener { public void newOutgoingCall(Intent intent) { - if (Intent.ACTION_CALL.equalsIgnoreCase(intent.getAction())) { - mAddress.setText(intent.getData().getSchemeSpecificPart()); - } else if (Intent.ACTION_SENDTO.equals(intent.getAction())) { + String scheme = intent.getData().getScheme(); + if (scheme.startsWith("imto")) { mAddress.setText("sip:" + intent.getData().getLastPathSegment()); + } else if (scheme.startsWith("call") || scheme.startsWith("sip")) { + mAddress.setText(intent.getData().getSchemeSpecificPart()); + } else { + Log.e("Unknown scheme: ",scheme); + mAddress.setText(intent.getData().getSchemeSpecificPart()); } mAddress.clearDisplayedName(); intent.setData(null); + // Setting data to null is no use when the activity is recreated + // as the intent is immutable. + // https://groups.google.com/forum/#!topic/android-developers/vrLdM5mKeoY + mPreventDoubleCallOnRotation=true; + setIntent(intent); LinphoneManager.getInstance().newOutgoingCall(mAddress); } diff --git a/src/org/linphone/IncallActivity.java b/src/org/linphone/IncallActivity.java index 6711cd0a3..91cda16cd 100644 --- a/src/org/linphone/IncallActivity.java +++ b/src/org/linphone/IncallActivity.java @@ -456,6 +456,7 @@ public class IncallActivity extends AbstractCalleesActivity implements boolean statusPaused = state== State.Paused || state == State.PausedByRemote; setVisibility(v, R.id.callee_status_paused, statusPaused); + setVisibility(v, R.id.QoS, !statusPaused); final OnLongClickListener showCallActionsLongListener = new OnLongClickListener() { public boolean onLongClick(View v) { @@ -538,6 +539,7 @@ public class IncallActivity extends AbstractCalleesActivity implements } registerCallDurationTimer(v, call); + registerCallQualityListener(v, call); enableView(v, R.id.callee_status_details, showCallActionsSimpleListener, true); diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index 4e9bb30d3..f807cd420 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -87,9 +87,10 @@ public class LinphoneActivity extends TabActivity implements ContactPicked super.onCreate(savedInstanceState); if (!LinphoneManager.isInstanciated()) { - Log.e("No service running: avoid crash by finishing ", this.getClass().getName()); + Log.e("No service running: avoid crash by starting the launcher", this.getClass().getName()); // super.onCreate called earlier finish(); + startActivity(getIntent().setClass(this, LinphoneLauncherActivity.class)); return; } instance = this; diff --git a/src/org/linphone/LinphoneManager.java b/src/org/linphone/LinphoneManager.java index 123acbcd0..2d7f60f0c 100644 --- a/src/org/linphone/LinphoneManager.java +++ b/src/org/linphone/LinphoneManager.java @@ -403,6 +403,47 @@ public final class LinphoneManager implements LinphoneCoreListener { } } + + private boolean isTunnelNeeded(NetworkInfo info) { + if (info == null) { + Log.i("No connectivity: tunnel should be disabled"); + return false; + } + + String pref = getPrefString(R.string.pref_tunnel_mode_key, R.string.default_tunnel_mode_entry_value); + + if (getString(R.string.tunnel_mode_entry_value_always).equals(pref)) { + return true; + } + + if (info.getType() != ConnectivityManager.TYPE_WIFI + && getString(R.string.tunnel_mode_entry_value_3G_only).equals(pref)) { + Log.i("need tunnel: 'no wifi' connection"); + return true; + } + + return false; + } + + public void manageTunnelServer(NetworkInfo info) { + if (mLc == null) return; + if (!mLc.isTunnelAvailable()) return; + + Log.i("Managing tunnel"); + if (isTunnelNeeded(info)) { + Log.i("Tunnel need to be activated"); + mLc.tunnelEnable(true); + } else { + Log.i("Tunnel should not be used"); + String pref = getPrefString(R.string.pref_tunnel_mode_key, R.string.default_tunnel_mode_entry_value); + mLc.tunnelEnable(false); + if (getString(R.string.tunnel_mode_entry_value_auto).equals(pref)) { + mLc.tunnelAutoDetect(); + } + } + } + + private synchronized void startLibLinphone() { try { copyAssetsFromPackage(); @@ -484,7 +525,7 @@ public final class LinphoneManager implements LinphoneCoreListener { void initMediaEncryption(){ String pref = getPrefString(R.string.pref_media_encryption_key, - getString(R.string.pref_media_encryption_key_none)); + R.string.pref_media_encryption_key_none); MediaEncryption me=MediaEncryption.None; if (pref.equals(getString(R.string.pref_media_encryption_key_srtp))) me=MediaEncryption.SRTP; @@ -494,11 +535,26 @@ public final class LinphoneManager implements LinphoneCoreListener { mLc.setMediaEncryption(me); } + private void initFromConfTunnel(){ + if (!mLc.isTunnelAvailable()) return; + NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); + mLc.tunnelEnableLogs(getPrefBoolean(R.string.pref_debug_key, false)); + mLc.tunnelCleanServers(); + String host = getString(R.string.tunnel_host); + if (host == null || host.length() == 0) + host = mPref.getString(getString(R.string.pref_tunnel_host_key), ""); + int port = Integer.parseInt(getPrefString(R.string.pref_tunnel_port_key, "443")); + mLc.tunnelAddServerAndMirror(host, port, 12345,500); + manageTunnelServer(info); + } + public void initFromConf() throws LinphoneConfigException { //traces boolean lIsDebug = true;//mPref.getBoolean(getString(R.string.pref_debug_key), false); LinphoneCoreFactory.instance().setDebugMode(lIsDebug); - + + initFromConfTunnel(); + if (initialTransports == null) initialTransports = mLc.getSignalingTransportPorts(); @@ -713,9 +769,32 @@ public final class LinphoneManager implements LinphoneCoreListener { private String getPrefString(int key, String value) { return mPref.getString(mR.getString(key), value); } + private String getPrefString(int key, int value) { + return mPref.getString(mR.getString(key), mR.getString(value)); + } + /* Simple implementation as Android way seems very complicate: + For example: with wifi and mobile actives; when pulling mobile down: + I/Linphone( 8397): WIFI connected: setting network reachable + I/Linphone( 8397): new state [RegistrationProgress] + I/Linphone( 8397): mobile disconnected: setting network unreachable + I/Linphone( 8397): Managing tunnel + I/Linphone( 8397): WIFI connected: setting network reachable + */ + public void connectivityChanged(NetworkInfo eventInfo, ConnectivityManager cm) { + NetworkInfo activeInfo = cm.getActiveNetworkInfo(); + + if (eventInfo.getState() == NetworkInfo.State.DISCONNECTED) { + Log.i(eventInfo.getTypeName()," disconnected: setting network unreachable"); + mLc.setNetworkReachable(false); + } else if (eventInfo.getState() == NetworkInfo.State.CONNECTED){ + manageTunnelServer(activeInfo); + Log.i(eventInfo.getTypeName()," connected: setting network reachable"); + mLc.setNetworkReachable(true); + } + } diff --git a/src/org/linphone/LinphonePreferencesActivity.java b/src/org/linphone/LinphonePreferencesActivity.java index 077fc0aa0..5c9f58555 100644 --- a/src/org/linphone/LinphonePreferencesActivity.java +++ b/src/org/linphone/LinphonePreferencesActivity.java @@ -52,6 +52,7 @@ import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; @@ -144,6 +145,15 @@ public class LinphonePreferencesActivity extends PreferenceActivity implements E if (Hacks.needSoftvolume()) checkAndDisableCheckbox(R.string.pref_audio_soft_volume_key); + if (!LinphoneManager.getLc().isTunnelAvailable()){ + hidePreferenceCategory(R.string.pref_tunnel_key); + } + } + + private void hidePreferenceCategory(int key) { + PreferenceCategory p = (PreferenceCategory) findPreference(key); + p.removeAll(); + p.setLayoutResource(R.layout.hidden); } private void doOnFirstLaunch() { diff --git a/src/org/linphone/LinphoneService.java b/src/org/linphone/LinphoneService.java index 6e3c037df..c1a479ab2 100644 --- a/src/org/linphone/LinphoneService.java +++ b/src/org/linphone/LinphoneService.java @@ -414,7 +414,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis sendNotification(IC_LEVEL_ORANGE, R.string.notification_registered); } - if (state == RegistrationState.RegistrationFailed) { + if (state == RegistrationState.RegistrationFailed || state == RegistrationState.RegistrationCleared) { sendNotification(IC_LEVEL_OFFLINE, R.string.notification_register_failure); } diff --git a/src/org/linphone/NetworkManager.java b/src/org/linphone/NetworkManager.java index fd7a93bd3..9714ceaed 100644 --- a/src/org/linphone/NetworkManager.java +++ b/src/org/linphone/NetworkManager.java @@ -51,11 +51,15 @@ public class NetworkManager extends BroadcastReceiver { if (lNoConnectivity | ((lNetworkInfo.getState() == NetworkInfo.State.DISCONNECTED) /*&& !lIsFailOver*/)) { LinphoneManager.getLc().setNetworkReachable(false); - } else if (lNetworkInfo.getState() == NetworkInfo.State.CONNECTED){ + } else if (lNetworkInfo.getState() == NetworkInfo.State.CONNECTED){ LinphoneManager.getLc().setNetworkReachable(true); - } else { + } else { // Other unhandled events - } + } + + NetworkInfo eventInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + LinphoneManager.getInstance().connectivityChanged(eventInfo, cm); } } diff --git a/src/org/linphone/VideoCallActivity.java b/src/org/linphone/VideoCallActivity.java index 0a08a6f7a..ce6b307c3 100755 --- a/src/org/linphone/VideoCallActivity.java +++ b/src/org/linphone/VideoCallActivity.java @@ -99,7 +99,9 @@ public class VideoCallActivity extends Activity implements LinphoneOnCallStateCh } public void onVideoPreviewSurfaceDestroyed(AndroidVideoWindowImpl vw) { - + // Remove references kept in jni code and restart camera + // LinphoneManager.getLc().setPreviewWindow(null); + // Commented to remove flicker. } }); @@ -272,7 +274,9 @@ public class VideoCallActivity extends Activity implements LinphoneOnCallStateCh @Override protected void onDestroy() { - androidVideoWindowImpl.release(); + if (androidVideoWindowImpl != null) { // Prevent linphone from crashing if correspondent hang up while you are rotating + androidVideoWindowImpl.release(); + } super.onDestroy(); } @@ -290,9 +294,7 @@ public class VideoCallActivity extends Activity implements LinphoneOnCallStateCh */ LinphoneManager.getLc().setVideoWindow(null); } - - LinphoneManager.getLc().setPreviewWindow(null); - + if (mCallQualityUpdater!=null){ refreshHandler.removeCallbacks(mCallQualityUpdater); mCallQualityUpdater=null; diff --git a/src/org/linphone/core/LinphoneCoreFactoryImpl.java b/src/org/linphone/core/LinphoneCoreFactoryImpl.java index 1591a8b4e..39fbeee54 100644 --- a/src/org/linphone/core/LinphoneCoreFactoryImpl.java +++ b/src/org/linphone/core/LinphoneCoreFactoryImpl.java @@ -49,6 +49,9 @@ public class LinphoneCoreFactoryImpl extends LinphoneCoreFactory { loadOptionalLibrary("srtp"); loadOptionalLibrary("zrtpcpp"); // GPLv3+ + // Tunnel + loadOptionalLibrary("tunnelclient"); + //Main library System.loadLibrary("linphone"); diff --git a/src/org/linphone/core/LinphoneCoreImpl.java b/src/org/linphone/core/LinphoneCoreImpl.java index 61ddbd4d6..5d04d745c 100644 --- a/src/org/linphone/core/LinphoneCoreImpl.java +++ b/src/org/linphone/core/LinphoneCoreImpl.java @@ -617,8 +617,43 @@ class LinphoneCoreImpl implements LinphoneCore { } private native void setPlayFile(long nativePtr, String path); + @Override public void setPlayFile(String path) { setPlayFile(nativePtr, path); } + + + private native void tunnelAddServerAndMirror(long nativePtr, String host, int port, int mirror, int ms); + @Override + public void tunnelAddServerAndMirror(String host, int port, int mirror, int ms) { + tunnelAddServerAndMirror(nativePtr, host, port, mirror, ms); + } + + private native void tunnelAutoDetect(long nativePtr); + @Override + public void tunnelAutoDetect() { + tunnelAutoDetect(nativePtr); + } + + private native void tunnelCleanServers(long nativePtr); + @Override + public void tunnelCleanServers() { + tunnelCleanServers(nativePtr); + } + + private native void tunnelEnable(long nativePtr, boolean enable); + @Override + public void tunnelEnable(boolean enable) { + tunnelEnable(nativePtr, enable); + } + + private native void tunnelEnableLogs(long nativePtr, boolean enable); + @Override + public void tunnelEnableLogs(boolean enable) { + tunnelEnableLogs(nativePtr, enable); + } + + @Override + public native boolean isTunnelAvailable(); } diff --git a/submodules/externals/build/exosip/Android.mk b/submodules/externals/build/exosip/Android.mk index c0698c5cc..979de4b82 100755 --- a/submodules/externals/build/exosip/Android.mk +++ b/submodules/externals/build/exosip/Android.mk @@ -56,7 +56,9 @@ LOCAL_CFLAGS += \ -UHAVE_CONFIG_H \ -include $(LOCAL_PATH)/../build/exosip/libeXosip2_AndroidConfig.h \ -DOSIP_MT \ - -DENABLE_TRACE + -DENABLE_TRACE \ + -include $(LOCAL_PATH)/include/eXosip2/eXosip_transport_hook.h + LOCAL_C_INCLUDES += \ $(LOCAL_PATH)/include \ diff --git a/submodules/externals/exosip b/submodules/externals/exosip index 4eabd05b1..c3da03035 160000 --- a/submodules/externals/exosip +++ b/submodules/externals/exosip @@ -1 +1 @@ -Subproject commit 4eabd05b149d18629098f3254f15c8d14ff6664c +Subproject commit c3da0303519ad3120355cb85baee9cf6d0e4d1c2 diff --git a/submodules/linphone b/submodules/linphone index ad8a8bbbb..5bf95231b 160000 --- a/submodules/linphone +++ b/submodules/linphone @@ -1 +1 @@ -Subproject commit ad8a8bbbb3fe7189111c38eefe1320ae21a64f02 +Subproject commit 5bf95231b51641ce095af49a864cf00fa6837750