diff --git a/Makefile b/Makefile index 6e6e5e128..09345fd3b 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ prepare-mediastreamer2: prepare-sources: prepare-ffmpeg prepare-ilbc prepare-vpx prepare-silk prepare-srtp prepare-mediastreamer2 generate-libs: - $(NDK_PATH)/ndk-build LINPHONE_VERSION=$(LINPHONE_VERSION) BUILD_SILK=1 BUILD_AMRNB=full -j$(NUMCPUS) + $(NDK_PATH)/ndk-build LINPHONE_VERSION=$(LINPHONE_VERSION) BUILD_SILK=1 BUILD_AMRNB=full BUILD_GPLV3_ZRTP=1 -j$(NUMCPUS) update-project: $(SDK_PATH)/android update project --path . diff --git a/res/drawable/zrtp_popup.9.png b/res/drawable/zrtp_popup.9.png new file mode 100644 index 000000000..8d117ff49 Binary files /dev/null and b/res/drawable/zrtp_popup.9.png differ diff --git a/res/layout/status.xml b/res/layout/status.xml index b87e1630b..17f4f5bce 100644 --- a/res/layout/status.xml +++ b/res/layout/status.xml @@ -78,6 +78,7 @@ + + + + + + + diff --git a/res/layout/zrtp_dialog.xml b/res/layout/zrtp_dialog.xml new file mode 100644 index 000000000..ab6211c02 --- /dev/null +++ b/res/layout/zrtp_dialog.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + diff --git a/src/org/linphone/InCallActivity.java b/src/org/linphone/InCallActivity.java index e60e2d136..a6a9224d2 100644 --- a/src/org/linphone/InCallActivity.java +++ b/src/org/linphone/InCallActivity.java @@ -38,7 +38,9 @@ import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; +import android.view.Gravity; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -49,6 +51,7 @@ import android.view.animation.AnimationUtils; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; /** @@ -289,9 +292,7 @@ public class InCallActivity extends FragmentActivity implements setCallControlsVisibleAndRemoveCallbacks(); } else { - if (!call.getCurrentParamsCopy().getVideoEnabled()) { - LinphoneManager.getInstance().addVideo(); - } + LinphoneManager.getInstance().addVideo(); isSpeakerEnabled = true; LinphoneManager.getInstance().routeAudioToSpeaker(); @@ -640,8 +641,8 @@ public class InCallActivity extends FragmentActivity implements @Override public void onCallStateChanged(final LinphoneCall call, State state, String message) { - if (state==State.Error){ - showToast(R.string.call_error, message); + if (state==State.Error) { + displayCustomToast(null, message, Toast.LENGTH_LONG); } if (LinphoneManager.getLc().getCallsNb() == 0) { @@ -681,11 +682,28 @@ public class InCallActivity extends FragmentActivity implements transfer.setEnabled(LinphoneManager.getLc().getCurrentCall() != null); } - private void showToast(int id, String txt) { - final String msg = String.format(getString(id), txt); + private void displayCustomToast(final String title, final String message, final int duration) { mHandler.post(new Runnable() { + @Override public void run() { - Toast.makeText(InCallActivity.this, msg, Toast.LENGTH_SHORT).show(); + LayoutInflater inflater = getLayoutInflater(); + View layout = inflater.inflate(R.layout.toast, (ViewGroup) findViewById(R.id.toastRoot)); + + TextView toastTitle = (TextView) layout.findViewById(R.id.toastTitle); + if (title == null) { + toastTitle.setVisibility(View.GONE); + } else { + toastTitle.setText(title); + } + + TextView toastText = (TextView) layout.findViewById(R.id.toastMessage); + toastText.setText(message); + + final Toast toast = new Toast(getApplicationContext()); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.setDuration(duration); + toast.setView(layout); + toast.show(); } }); } diff --git a/src/org/linphone/StatusFragment.java b/src/org/linphone/StatusFragment.java index bdbd8e748..2676002a5 100644 --- a/src/org/linphone/StatusFragment.java +++ b/src/org/linphone/StatusFragment.java @@ -32,9 +32,11 @@ import org.linphone.ui.SlidingDrawer.OnDrawerOpenListener; import android.app.Activity; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.CountDownTimer; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -44,6 +46,7 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; +import android.widget.Toast; /** * @author Sylvain Berfini @@ -59,6 +62,10 @@ public class StatusFragment extends Fragment { private Runnable mCallQualityUpdater; private boolean isInCall, isAttached = false; + private Toast zrtpToast; + private CountDownTimer zrtpHack; + private boolean hideZrtpToast = false; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -85,7 +92,9 @@ public class StatusFragment extends Fragment { exit.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - LinphoneActivity.instance().exit(); + if (LinphoneActivity.isInstanciated()) { + LinphoneActivity.instance().exit(); + } } }); @@ -267,9 +276,9 @@ public class StatusFragment extends Fragment { @Override public void onResume() { super.onResume(); - - if (isInCall) { - LinphoneCall call = LinphoneManager.getLc().getCurrentCall(); + + LinphoneCall call = LinphoneManager.getLc().getCurrentCall(); + if (isInCall && call != null) { startCallQuality(); refreshStatusItems(call, call.getCurrentParamsCopy().getVideoEnabled()); @@ -293,16 +302,19 @@ public class StatusFragment extends Fragment { public void onPause() { super.onPause(); + if (zrtpToast != null) { + hideZRTPDialog(); + } + if (mCallQualityUpdater != null) { refreshHandler.removeCallbacks(mCallQualityUpdater); mCallQualityUpdater = null; } } - public void refreshStatusItems(LinphoneCall call, boolean isVideoEnabled) { + public void refreshStatusItems(final LinphoneCall call, boolean isVideoEnabled) { if (call != null && encryption != null) { MediaEncryption mediaEncryption = call.getCurrentParamsCopy().getMediaEncryption(); - Log.e("MediaEncryption = " + mediaEncryption); exit.setVisibility(View.GONE); statusText.setVisibility(View.GONE); @@ -311,6 +323,9 @@ public class StatusFragment extends Fragment { } encryption.setVisibility(View.VISIBLE); + Log.e("MediaEncryption = " + mediaEncryption); + Log.e("TokenVerified = " + call.isAuthenticationTokenVerified()); + if (mediaEncryption == MediaEncryption.SRTP || (mediaEncryption == MediaEncryption.ZRTP && call.isAuthenticationTokenVerified())) { encryption.setImageResource(R.drawable.security_ok); } else if (mediaEncryption == MediaEncryption.ZRTP && !call.isAuthenticationTokenVerified()) { @@ -318,6 +333,17 @@ public class StatusFragment extends Fragment { } else { encryption.setImageResource(R.drawable.security_ko); } + + if (mediaEncryption == MediaEncryption.ZRTP) { + encryption.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showZRTPDialog(call); + } + }); + } else { + encryption.setOnClickListener(null); + } } else { exit.setVisibility(View.VISIBLE); statusText.setVisibility(View.VISIBLE); @@ -326,6 +352,82 @@ public class StatusFragment extends Fragment { } } + private void hideZRTPDialog() { + hideZrtpToast = true; + + if (zrtpToast != null) { + zrtpToast.cancel(); + } + if (zrtpHack != null) { + zrtpHack.cancel(); + } + } + + private void showZRTPDialog(final LinphoneCall call) { + boolean authVerified = call.isAuthenticationTokenVerified(); + String format = getString(authVerified ? R.string.reset_sas_fmt : R.string.verify_sas_fmt); + + LayoutInflater inflater = LayoutInflater.from(getActivity()); + View layout = inflater.inflate(R.layout.zrtp_dialog, (ViewGroup) getActivity().findViewById(R.id.toastRoot)); + + TextView toastText = (TextView) layout.findViewById(R.id.toastMessage); + toastText.setText(String.format(format, call.getAuthenticationToken())); + + zrtpToast = new Toast(getActivity()); + zrtpToast.setGravity(Gravity.TOP | Gravity.RIGHT, 0, LinphoneUtils.pixelsToDpi(getResources(), 40)); + zrtpToast.setDuration(Toast.LENGTH_LONG); + zrtpToast.setView(layout); + + ImageView ok = (ImageView) layout.findViewById(R.id.toastOK); + ok.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (call != null) { + call.setAuthenticationTokenVerified(true); + } + if (encryption != null) { + encryption.setImageResource(R.drawable.security_ok); + } + hideZRTPDialog(); + } + }); + + ImageView notOk = (ImageView) layout.findViewById(R.id.toastNotOK); + notOk.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (call != null) { + call.setAuthenticationTokenVerified(false); + } + if (encryption != null) { + encryption.setImageResource(R.drawable.security_pending); + } + hideZRTPDialog(); + } + }); + + zrtpHack = new CountDownTimer(3000, 1000) + { + public void onTick(long millisUntilFinished) + { + if (!hideZrtpToast) { + zrtpToast.show(); + } + } + public void onFinish() { + if (!hideZrtpToast) { + zrtpToast.show(); + zrtpHack.start(); + } + } + + }; + + zrtpToast.show(); + hideZrtpToast = false; + zrtpHack.start(); + } + class AccountsListAdapter extends BaseAdapter { private SharedPreferences prefs; private List checkboxes; @@ -363,7 +465,12 @@ public class StatusFragment extends Fragment { }; public int getCount() { - return LinphoneManager.getLc().getProxyConfigList().length; + LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); + if (lc != null) { + return lc.getProxyConfigList().length; + } else { + return 0; + } } public Object getItem(int position) {