From d9c5e6d786c024ec2d2ba841e24802a5daa8d1b9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 27 Apr 2016 14:55:43 +0200 Subject: [PATCH 01/24] android: Fix handling of redirects during IKE_AUTH --- .../backend/android_service.c | 153 ++++++++++-------- 1 file changed, 84 insertions(+), 69 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index b3e91d808..3db0f744d 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -1,8 +1,8 @@ /* - * Copyright (C) 2010-2015 Tobias Brunner + * Copyright (C) 2010-2016 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * 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 @@ -430,6 +430,84 @@ CALLBACK(reestablish, job_requeue_t, return JOB_REQUEUE_NONE; } +METHOD(listener_t, ike_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, bool up) +{ + /* this callback is only registered during initiation, so if the IKE_SA + * goes down we assume some kind of authentication error, more specific + * errors are catched in the alert() handler */ + if (this->ike_sa == ike_sa && !up) + { + charonservice->update_status(charonservice, + CHARONSERVICE_AUTH_ERROR); + return FALSE; + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + this->ike_sa = new; + } + return TRUE; +} + +METHOD(listener_t, ike_reestablish_post_redirect, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, + bool initiated) +{ + if (this->ike_sa == old && initiated) + { /* if we get redirected during IKE_AUTH we just migrate to the new SA, + * we don't have a TUN device yet, so reinstalling it without DNS would + * fail (and using the DNS proxy is not required anyway) */ + this->ike_sa = new; + } + return TRUE; +} + +METHOD(listener_t, ike_reestablish_pre, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + /* enable DNS proxy so hosts are properly resolved while the TUN device + * is still active */ + this->lock->write_lock(this->lock); + this->use_dns_proxy = TRUE; + this->lock->unlock(this->lock); + /* if DNS servers are installed that are only reachable through the VPN + * the DNS proxy doesn't help, so uninstall DNS servers */ + if (!setup_tun_device_without_dns(this)) + { + DBG1(DBG_DMN, "failed to setup TUN device without DNS"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + } + } + return TRUE; +} + +METHOD(listener_t, ike_reestablish_post, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, + bool initiated) +{ + if (this->ike_sa == old && initiated) + { + this->ike_sa = new; + /* re-register hook to detect initiation failures */ + this->public.listener.ike_updown = _ike_updown; + /* if the IKE_SA got deleted by the responder we get the child_down() + * event on the old IKE_SA after this hook has been called, so they + * get ignored and thus we trigger the event here */ + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_DOWN); + } + return TRUE; +} + METHOD(listener_t, child_updown, bool, private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) @@ -440,6 +518,9 @@ METHOD(listener_t, child_updown, bool, { /* disable the hooks registered to catch initiation failures */ this->public.listener.ike_updown = NULL; + /* enable hooks to handle reauthentications */ + this->public.listener.ike_reestablish_pre = _ike_reestablish_pre; + this->public.listener.ike_reestablish_post = _ike_reestablish_post; /* CHILD_SA is up so we can disable the DNS proxy we enabled to * reestablish the SA */ this->lock->write_lock(this->lock); @@ -465,21 +546,6 @@ METHOD(listener_t, child_updown, bool, return TRUE; } -METHOD(listener_t, ike_updown, bool, - private_android_service_t *this, ike_sa_t *ike_sa, bool up) -{ - /* this callback is only registered during initiation, so if the IKE_SA - * goes down we assume some kind of authentication error, more specific - * errors are catched in the alert() handler */ - if (this->ike_sa == ike_sa && !up) - { - charonservice->update_status(charonservice, - CHARONSERVICE_AUTH_ERROR); - return FALSE; - } - return TRUE; -} - METHOD(listener_t, alert, bool, private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) @@ -554,56 +620,6 @@ METHOD(listener_t, alert, bool, return TRUE; } -METHOD(listener_t, ike_rekey, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) -{ - if (this->ike_sa == old) - { - this->ike_sa = new; - } - return TRUE; -} - -METHOD(listener_t, ike_reestablish_pre, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) -{ - if (this->ike_sa == old) - { - /* enable DNS proxy so hosts are properly resolved while the TUN device - * is still active */ - this->lock->write_lock(this->lock); - this->use_dns_proxy = TRUE; - this->lock->unlock(this->lock); - /* if DNS servers are installed that are only reachable through the VPN - * the DNS proxy doesn't help, so uninstall DNS servers */ - if (!setup_tun_device_without_dns(this)) - { - DBG1(DBG_DMN, "failed to setup TUN device without DNS"); - charonservice->update_status(charonservice, - CHARONSERVICE_GENERIC_ERROR); - } - } - return TRUE; -} - -METHOD(listener_t, ike_reestablish_post, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, - bool initiated) -{ - if (this->ike_sa == old && initiated) - { - this->ike_sa = new; - /* re-register hook to detect initiation failures */ - this->public.listener.ike_updown = _ike_updown; - /* if the IKE_SA got deleted by the responder we get the child_down() - * event on the old IKE_SA after this hook has been called, so they - * get ignored and thus we trigger the event here */ - charonservice->update_status(charonservice, - CHARONSERVICE_CHILD_STATE_DOWN); - } - return TRUE; -} - static void add_auth_cfg_pw(private_android_service_t *this, peer_cfg_t *peer_cfg, bool byod) { @@ -824,8 +840,7 @@ android_service_t *android_service_create(android_creds_t *creds, .public = { .listener = { .ike_rekey = _ike_rekey, - .ike_reestablish_pre = _ike_reestablish_pre, - .ike_reestablish_post = _ike_reestablish_post, + .ike_reestablish_post = _ike_reestablish_post_redirect, .ike_updown = _ike_updown, .child_updown = _child_updown, .alert = _alert, From 1bd213db79f6ed1666f22c82ca99added38eeb89 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 27 Apr 2016 15:11:54 +0200 Subject: [PATCH 02/24] android: Use relative path for strongSwan sources This avoids issues with recursion, which could have happened if the strongswan directory was a symlink. --- .../android/app/src/main/jni/Android.mk | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/Android.mk b/src/frontends/android/app/src/main/jni/Android.mk index 9e81a14c5..5bbfeaa1b 100644 --- a/src/frontends/android/app/src/main/jni/Android.mk +++ b/src/frontends/android/app/src/main/jni/Android.mk @@ -16,12 +16,14 @@ endif strongswan_PLUGINS := $(strongswan_CHARON_PLUGINS) \ $(strongswan_BYOD_PLUGINS) -include $(LOCAL_PATH)/strongswan/Android.common.mk +strongswan_DIR := ../../../../../../../ # includes -strongswan_PATH := $(LOCAL_PATH)/strongswan +strongswan_PATH := $(LOCAL_PATH)/$(strongswan_DIR) openssl_PATH := $(LOCAL_PATH)/openssl/include +include $(strongswan_PATH)/Android.common.mk + # CFLAGS (partially from a configure run using droid-gcc) strongswan_CFLAGS := \ -Wall \ @@ -67,15 +69,15 @@ endif strongswan_BUILD := \ openssl \ libandroidbridge \ - strongswan/src/libipsec \ - strongswan/src/libcharon \ - strongswan/src/libstrongswan + $(strongswan_DIR)/src/libipsec \ + $(strongswan_DIR)/src/libcharon \ + $(strongswan_DIR)/src/libstrongswan ifneq ($(strongswan_USE_BYOD),) strongswan_BUILD += \ - strongswan/src/libtnccs \ - strongswan/src/libtncif \ - strongswan/src/libimcv + $(strongswan_DIR)/src/libtnccs \ + $(strongswan_DIR)/src/libtncif \ + $(strongswan_DIR)/src/libimcv endif include $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \ From 3256fe9ebb0c530a2ecf4ac977a32c8e099dc3ca Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 27 Apr 2016 15:21:03 +0200 Subject: [PATCH 03/24] android: Update README.ndk --- src/frontends/android/README.ndk | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/frontends/android/README.ndk b/src/frontends/android/README.ndk index 7c8cd309e..22150dd55 100644 --- a/src/frontends/android/README.ndk +++ b/src/frontends/android/README.ndk @@ -1,13 +1,14 @@ -To build this within the NDK several things have to be added in the -app/src/main/jni/ folder: +To build this within the NDK the following things have to be done: - - strongswan: The strongSwan sources. This can either be an extracted tarball, - or a symlink to the Git repository. To build from the repository the sources - have to be prepared first (see HACKING for a list of required tools): + - By default the strongSwan sources of the current Git tree are used. They have + to be prepared first (see HACKING for a list of required tools): ./autogen.sh && ./configure && make && make distclean - - openssl: The OpenSSL sources. Since the sources need to be changed to be - built on Android (and especially in the NDK), we provide a modified mirror - of the official Android OpenSSL version on git.strongswan.org. + It is also possible to use the sources from a different directory (e.g. an + extracted tarball) by setting strongswan_DIR in app/src/main/jni/Android.mk. + - The OpenSSL or BoringSSL sources are expected in app/src/main/jni/openssl. + Since the sources need to be changed to be built on Android (and especially + in the NDK) we provide a modified mirror of the official Android repositories + on git.strongswan.org. From 7c5fec3a5a9a62a5c42d20c5347196f58909fd7c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 28 Apr 2016 09:21:06 +0200 Subject: [PATCH 04/24] android: Use Fragment class from the support library to avoid deprecation warnings For instance, onAttach() with an Activitiy as first argument was deprecated with API level 23. However, the overload with a Context as first argument does obviously not get called on older API levels. Luckily, the classes provided by the support library handle that for us. --- .../android/ui/ImcStateFragment.java | 6 ++-- .../strongswan/android/ui/LogFragment.java | 24 ++++++------- .../strongswan/android/ui/MainActivity.java | 8 ++--- .../ui/RemediationInstructionFragment.java | 2 +- .../ui/RemediationInstructionsActivity.java | 10 +++--- .../ui/RemediationInstructionsFragment.java | 20 +++++------ .../android/ui/VpnProfileListFragment.java | 34 +++++++++---------- .../android/ui/VpnStateFragment.java | 2 +- 8 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java index 7328693f1..bf5eaf64d 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/ImcStateFragment.java @@ -15,9 +15,6 @@ package org.strongswan.android.ui; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; import android.app.Service; import android.content.ComponentName; import android.content.Context; @@ -25,6 +22,9 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.view.GestureDetector; import android.view.LayoutInflater; diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java index 8740e0c46..2f6240726 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/LogFragment.java @@ -15,24 +15,24 @@ package org.strongswan.android.ui; +import android.os.Bundle; +import android.os.FileObserver; +import android.os.Handler; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.strongswan.android.R; +import org.strongswan.android.logic.CharonVpnService; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.StringReader; -import org.strongswan.android.R; -import org.strongswan.android.logic.CharonVpnService; - -import android.app.Fragment; -import android.os.Bundle; -import android.os.FileObserver; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - public class LogFragment extends Fragment implements Runnable { private String mLogFilePath; diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java index 3d5324056..a6c668104 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/MainActivity.java @@ -18,9 +18,6 @@ package org.strongswan.android.ui; import android.app.Dialog; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; import android.app.Service; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -31,6 +28,9 @@ import android.net.VpnService; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; @@ -317,7 +317,7 @@ public class MainActivity extends AppCompatActivity implements OnVpnProfileSelec */ public void removeFragmentByTag(String tag) { - FragmentManager fm = getFragmentManager(); + FragmentManager fm = getSupportFragmentManager(); Fragment login = fm.findFragmentByTag(tag); if (login != null) { diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java index 04c288bcf..9223479b6 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java @@ -18,8 +18,8 @@ package org.strongswan.android.ui; import org.strongswan.android.R; import org.strongswan.android.logic.imc.RemediationInstruction; -import android.app.ListFragment; import android.os.Bundle; +import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java index 7e7e870be..7765a2e02 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsActivity.java @@ -38,7 +38,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement { /* only update if we're not restoring */ return; } - RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getFragmentManager().findFragmentById(R.id.remediation_instructions_fragment); + RemediationInstructionsFragment frag = (RemediationInstructionsFragment)getSupportFragmentManager().findFragmentById(R.id.remediation_instructions_fragment); if (frag != null) { /* two-pane layout, update fragment */ Bundle extras = getIntent().getExtras(); @@ -49,7 +49,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement { /* one-pane layout, create fragment */ frag = new RemediationInstructionsFragment(); frag.setArguments(getIntent().getExtras()); - getFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit(); + getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, frag).commit(); } } @@ -60,7 +60,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement { case android.R.id.home: /* one-pane layout, pop possible fragment from stack, finish otherwise */ - if (!getFragmentManager().popBackStackImmediate()) + if (!getSupportFragmentManager().popBackStackImmediate()) { finish(); } @@ -74,7 +74,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement @Override public void onRemediationInstructionSelected(RemediationInstruction instruction) { - RemediationInstructionFragment frag = (RemediationInstructionFragment)getFragmentManager().findFragmentById(R.id.remediation_instruction_fragment); + RemediationInstructionFragment frag = (RemediationInstructionFragment)getSupportFragmentManager().findFragmentById(R.id.remediation_instruction_fragment); if (frag != null) { /* two-pane layout, update directly */ @@ -87,7 +87,7 @@ public class RemediationInstructionsActivity extends AppCompatActivity implement args.putParcelable(RemediationInstructionFragment.ARG_REMEDIATION_INSTRUCTION, instruction); frag.setArguments(args); - getFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit(); + getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, frag).addToBackStack(null).commit(); getSupportActionBar().setTitle(instruction.getTitle()); } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java index 86467dc35..a2beffd94 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionsFragment.java @@ -15,17 +15,17 @@ package org.strongswan.android.ui; -import java.util.ArrayList; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.ListFragment; +import android.view.View; +import android.widget.ListView; import org.strongswan.android.R; import org.strongswan.android.logic.imc.RemediationInstruction; import org.strongswan.android.ui.adapter.RemediationInstructionAdapter; -import android.app.Activity; -import android.app.ListFragment; -import android.os.Bundle; -import android.view.View; -import android.widget.ListView; +import java.util.ArrayList; public class RemediationInstructionsFragment extends ListFragment { @@ -65,13 +65,13 @@ public class RemediationInstructionsFragment extends ListFragment } @Override - public void onAttach(Activity activity) + public void onAttach(Context context) { - super.onAttach(activity); + super.onAttach(context); - if (activity instanceof OnRemediationInstructionSelectedListener) + if (context instanceof OnRemediationInstructionSelectedListener) { - mListener = (OnRemediationInstructionSelectedListener)activity; + mListener = (OnRemediationInstructionSelectedListener)context; } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java index a23df054b..d8d99ff00 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileListFragment.java @@ -17,21 +17,12 @@ package org.strongswan.android.ui; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -import org.strongswan.android.R; -import org.strongswan.android.data.VpnProfile; -import org.strongswan.android.data.VpnProfileDataSource; -import org.strongswan.android.ui.adapter.VpnProfileAdapter; - import android.app.Activity; -import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.util.AttributeSet; import android.view.ActionMode; import android.view.LayoutInflater; @@ -46,6 +37,15 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast; +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.ui.adapter.VpnProfileAdapter; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + public class VpnProfileListFragment extends Fragment { private static final int ADD_REQUEST = 1; @@ -66,10 +66,10 @@ public class VpnProfileListFragment extends Fragment } @Override - public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { - super.onInflate(activity, attrs, savedInstanceState); - TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.Fragment); + super.onInflate(context, attrs, savedInstanceState); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Fragment); mReadOnly = a.getBoolean(R.styleable.Fragment_read_only, false); a.recycle(); } @@ -126,13 +126,13 @@ public class VpnProfileListFragment extends Fragment } @Override - public void onAttach(Activity activity) + public void onAttach(Context context) { - super.onAttach(activity); + super.onAttach(context); - if (activity instanceof OnVpnProfileSelectedListener) + if (context instanceof OnVpnProfileSelectedListener) { - mListener = (OnVpnProfileSelectedListener)activity; + mListener = (OnVpnProfileSelectedListener)context; } } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java index c49e5cc7e..10bf2c322 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnStateFragment.java @@ -17,7 +17,6 @@ package org.strongswan.android.ui; -import android.app.Fragment; import android.app.ProgressDialog; import android.app.Service; import android.content.ComponentName; @@ -27,6 +26,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; +import android.support.v4.app.Fragment; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; From 353526601aab6c5c5b78790a856d3beb0cc815c9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 28 Apr 2016 17:00:27 +0200 Subject: [PATCH 05/24] android: Fix display of remediation instructions with support library Because the support library creates its own layout manually and uses different IDs than the list_content layout we can't use the method we used previously (and which is actually recommended in the docs). --- .../ui/RemediationInstructionFragment.java | 17 ++++++++++++----- .../main/res/layout/remediation_instruction.xml | 6 +++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java index 9223479b6..c17c552a2 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/RemediationInstructionFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Tobias Brunner + * Copyright (C) 2013-2016 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -15,17 +15,18 @@ package org.strongswan.android.ui; -import org.strongswan.android.R; -import org.strongswan.android.logic.imc.RemediationInstruction; - import android.os.Bundle; import android.support.v4.app.ListFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.FrameLayout; import android.widget.TextView; +import org.strongswan.android.R; +import org.strongswan.android.logic.imc.RemediationInstruction; + public class RemediationInstructionFragment extends ListFragment { public static final String ARG_REMEDIATION_INSTRUCTION = "instruction"; @@ -37,7 +38,13 @@ public class RemediationInstructionFragment extends ListFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.remediation_instruction, container, false); + /* while the documentation recommends to include "@android:layout/list_content" to retain + * the default functionality, this does not actually work with the ListFragment provided by + * the support library as it builds the view manually and uses different IDs */ + View layout = inflater.inflate(R.layout.remediation_instruction, container, false); + FrameLayout list = (FrameLayout)layout.findViewById(R.id.list_container); + list.addView(super.onCreateView(inflater, list, savedInstanceState)); + return layout; } @Override diff --git a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml index 09c0d43a3..04fffaa4e 100644 --- a/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml +++ b/src/frontends/android/app/src/main/res/layout/remediation_instruction.xml @@ -1,6 +1,6 @@ @@ -25,6 +25,6 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" - strongswan:read_only="true" /> + app:read_only="true" /> \ No newline at end of file From 79ba4b285f4aa836346a7afe2bdb206e05d72304 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 29 Apr 2016 18:15:29 +0200 Subject: [PATCH 16/24] android: Add TextInputLayout child class that displays a helper text below the text field Also hides the error message if the text is changed. --- .../ui/widget/TextInputLayoutHelper.java | 180 ++++++++++++++++++ .../android/app/src/main/res/values/attrs.xml | 7 +- 2 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java new file mode 100644 index 000000000..45b0ae592 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/widget/TextInputLayoutHelper.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2016 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * 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. See . + * + * 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. + */ + +package org.strongswan.android.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputLayout; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.strongswan.android.R; + +/** + * Layout that extends {@link android.support.design.widget.TextInputLayout} with a helper text + * displayed below the text field when it receives the focus. Also, any error message shown with + * {@link #setError(CharSequence)} is hidden when the text field is changed (this mirrors the + * behavior of {@link android.widget.EditText}). + */ +public class TextInputLayoutHelper extends TextInputLayout +{ + private LinearLayout mHelperContainer; + private TextView mHelperText; + + public TextInputLayoutHelper(Context context) + { + this(context, null); + } + + public TextInputLayoutHelper(Context context, AttributeSet attrs) + { + this(context, attrs, 0); + } + + public TextInputLayoutHelper(Context context, AttributeSet attrs, int defStyleAttr) + { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TextInputLayoutHelper); + String helper = a.getString(R.styleable.TextInputLayoutHelper_helper_text); + a.recycle(); + if (helper != null) + { + mHelperContainer = new LinearLayout(context); + mHelperContainer.setOrientation(LinearLayout.HORIZONTAL); + mHelperContainer.setVisibility(View.INVISIBLE); + addView(mHelperContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + + mHelperText = new TextView(context); + mHelperText.setText(helper); + mHelperText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); + a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textColorSecondary}); + mHelperText.setTextColor(a.getColor(0, mHelperText.getCurrentTextColor())); + a.recycle(); + + mHelperContainer.addView(mHelperText, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) + { + super.addView(child, index, params); + if (child instanceof EditText) + { + EditText text = (EditText)child; + text.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) + { + if (getError() != null) + { + setError(null); + } + } + }); + if (mHelperContainer != null) + { + text.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) + { + showHelper(hasFocus); + } + }); + ViewCompat.setPaddingRelative(mHelperContainer, ViewCompat.getPaddingStart(text), + 0, ViewCompat.getPaddingEnd(text), text.getPaddingBottom()); + } + } + } + + @Override + public void setError(@Nullable CharSequence error) + { + super.setError(error); + if (mHelperContainer != null) + { + if (error == null) + { /* this frees up space used by the now invisible error message */ + setErrorEnabled(false); + } + else + { /* re-add the helper as the error message should be displayed directly under the textbox */ + removeView(mHelperContainer); + addView(mHelperContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + } + } + + /** + * Set the helper text to be displayed below the text field. + * + * @attr ref R.styleable#TextInputLayoutHelper_helper_text + */ + public void setHelperText(CharSequence text) + { + mHelperText.setText(text); + } + + private void showHelper(boolean show) + { + if (show == (mHelperContainer.getVisibility() == View.VISIBLE)) + { + return; + } + if (show) + { + ViewCompat.animate(mHelperContainer) + .alpha(1f) + .setDuration(200) + .setListener(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationStart(View view) + { + view.setVisibility(View.VISIBLE); + } + }).start(); + } + else + { + ViewCompat.animate(mHelperContainer) + .alpha(0f) + .setDuration(200) + .setListener(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) + { + view.setVisibility(View.INVISIBLE); + } + }).start(); + } + } +} diff --git a/src/frontends/android/app/src/main/res/values/attrs.xml b/src/frontends/android/app/src/main/res/values/attrs.xml index 6c3480b0e..592555c8c 100644 --- a/src/frontends/android/app/src/main/res/values/attrs.xml +++ b/src/frontends/android/app/src/main/res/values/attrs.xml @@ -1,7 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" > + android:padding="10dp" + android:animateLayoutChanges="true" > + + + + + + - - - - - + android:layout_height="wrap_content" > - + - + - + android:layout_marginTop="4dp" + app:helper_text="@string/profile_password_hint" > + + + + @@ -95,12 +107,15 @@ android:id="@+id/user_certificate_group" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginBottom="4dp" android:orientation="vertical" > - + android:layout_marginTop="8dp" + app:helper_text="@string/profile_name_hint" > - + + + + android:layout_marginLeft="4dp" + android:textSize="20sp" + android:text="@string/profile_advanced_label" /> - - - + app:helper_text="@string/profile_mtu_hint" > + + - + + + app:helper_text="@string/profile_port_hint" > + + + + - \ No newline at end of file + diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index 076bea3d3..c2357eb5c 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -49,30 +49,34 @@ Speichern Abbrechen - Profilname: - (Server-Adresse verwenden) - Server: - Typ: - Benutzername: - Passwort: - (anfordern wenn benötigt) - Benutzer-Zertifikat: + Profilname (optional) + Standardwert ist der konfigurierte Server + Standardwert ist \"%1$s\" + Server + IP-Adresse oder Hostname des VPN Servers + VPN-Typ + Benutzername + Passwort (optional) + Leer lassen, um bei Bedarf danach gefragt zu werden + Benutzer-Zertifikat Benutzer-Zertifikat auswählen Wählen Sie ein bestimmtes Benutzer-Zertifikat - CA-Zertifikat: + CA-Zertifikat Automatisch wählen CA-Zertifikat auswählen Wählen Sie ein bestimmtes CA-Zertifikat + Erweiterte Einstellungen Erweiterte Einstellungen anzeigen - MTU: - Server Port: - (Standardwert verwenden) - Split-Tunneling: + MTU des VPN Tunnel-Device + Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist + Server Port + UDP-Port zu dem verbunden wird, falls dieser vom Standard-Port abweicht + Split-Tunneling Blockiere IPv4 Verkehr der nicht für das VPN bestimmt ist Blockiere IPv6 Verkehr der nicht für das VPN bestimmt ist - Bitte geben Sie hier die Server-Adresse ein - Bitte geben Sie hier Ihren Benutzernamen ein + Ein Wert wird benötigt, um die Verbindung aufbauen zu können + Bitte geben Sie Ihren Benutzernamen ein Kein CA-Zertifikat ausgewählt Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen Bitte geben Sie eine Nummer von %1$d - %2$d ein diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index 6a1e3460f..c8c159537 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -49,36 +49,40 @@ Zapisz Anuluj - Nazwa profilu: - (użyj adresu serwer) - Serwer: - Typ: - Użytkownik: - Hasło: - (w razie potrzeby zapromptuj) - Certyfikat użytkownika: + Nazwa profilu (opcjonalny) + Defaults to the configured server + Defaults to \"%1$s\" + Serwer + IP address or hostname of the VPN server + Typ VPN + Użytkownik + Hasło (opcjonalny) + Leave blank to get prompted on demand + Certyfikat użytkownika Wybierz certyfikat użytkownika >Wybierz określony certyfikat użytkownika - Certyfikat CA: + Certyfikat CA Wybierz automatycznie Wybierz certyfikat CA Wybierz określony certyfikat CA + Advanced settings Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: + MTU of the VPN tunnel device + In case the default value is unsuitable for a particular network + Server port + UDP port to connect to, if different from the default + Split tunneling Block IPv4 traffic not destined for the VPN Block IPv6 traffic not destined for the VPN - Wprowadź adres serwer + A value is required to initiate the connection Wprowadź swoją nazwę użytkownika Nie wybrano żadnego certyfikatu CA Wybierz lub uaktywnij jeden Wybierz automatycznie Please enter a number in the range from %1$d - %2$d EAP-TNC may affect your privacy Device data is sent to the server operator - <p>Trusted Network Connect (TNC) allows server operators to assess the health of a client device.</p><p>For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.</p><b>Any data will be sent only after verifying the server\'s identity.</b> + Trusted Network Connect (TNC) allows server operators to assess the health of a client device.

For that purpose the server operator may request data such as a unique identifier, a list of installed packages, system settings, or cryptographic checksums of files.

Any data will be sent only after verifying the server\'s identity.]]>
Certyfikaty CA diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 098bdf923..0550fb292 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -46,29 +46,33 @@ Сохранить Отмена - Название профиля: - (адрес cервер) - Сервер: - Тип: - Логин: - Пароль: - (спросить если нужно) - Сертификат пользователя: + Название профиля (необязательный) + Defaults to the configured server + Defaults to \"%1$s\" + Сервер + IP address or hostname of the VPN server + VPN Тип + Логин + Пароль (необязательный) + Leave blank to get prompted on demand + Сертификат пользователя Выбрать сертификат пользователя Выбрать сертификат пользователя - Сертификат CA: + Сертификат CA Выбрать автоматически Выбрать сертификат CA Выбрать CA сертификат + Advanced settings Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: + MTU of the VPN tunnel device + In case the default value is unsuitable for a particular network + Server port + UDP port to connect to, if different from the default + Split tunneling Block IPv4 traffic not destined for the VPN Block IPv6 traffic not destined for the VPN - Пожалуйста введите адрес cервер + A value is required to initiate the connection Пожалуйста введите имя пользователя Не выбран сертификат CA Пожалуйста выберите один Выбрать автоматически @@ -127,4 +131,3 @@ Соединить - diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index d6b9772a2..a52074876 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -47,30 +47,34 @@ Зберегти Відміна - Назва профілю: - (використовувати адресу cервер) - Сервер: - Тип: - Логін: - Пароль: - (запитати якщо потрібно) - Сертифікат користувача: + Назва профілю (необов\'язковий) + Defaults to the configured server + Defaults to \"%1$s\" + Сервер + IP address or hostname of the VPN server + VPN Тип + Логін + Пароль (необов\'язковий) + Leave blank to get prompted on demand + Сертифікат користувача Виберіть сертифікат користувача Вибрати спеціальний сертифікат користувача - Сертифікат CA: + Сертифікат CA Вибрати автоматично Вибрати сертифікат CA Вибрати спеціальний сертифікат CA + Advanced settings Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: + MTU of the VPN tunnel device + In case the default value is unsuitable for a particular network + Server port + UDP port to connect to, if different from the default + Split tunneling Block IPv4 traffic not destined for the VPN Block IPv6 traffic not destined for the VPN - Введіть адресу cервер тут - Введіть ім\'я користувача тут + A value is required to initiate the connection + Введіть ім\'я користувача Не вибрано сертифікат CA Будь ласка виберіть один Вибрати автоматично Please enter a number in the range from %1$d - %2$d @@ -128,4 +132,3 @@ Підключити - diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index be90dc98d..76886cd6d 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -49,30 +49,34 @@ Save Cancel - Profile Name: - (use server address) - Server: - Type: - Username: - Password: - (prompt when needed) - User certificate: + Profile name (optional) + Defaults to the configured server + Defaults to \"%1$s\" + Server + IP address or hostname of the VPN server + VPN Type + Username + Password (optional) + Leave blank to get prompted on demand + User certificate Select user certificate Select a specific user certificate - CA certificate: + CA certificate Select automatically Select CA certificate Select a specific CA certificate + Advanced settings Show advanced settings - MTU: - Server port: - (use default) - Split tunneling: + MTU of the VPN tunnel device + In case the default value is unsuitable for a particular network + Server port + UDP port to connect to, if different from the default + Split tunneling Block IPv4 traffic not destined for the VPN Block IPv6 traffic not destined for the VPN - Please enter the server address here - Please enter your username here + A value is required to initiate the connection + Please enter your username No CA certificate selected Please select one or activate Select automatically Please enter a number in the range from %1$d - %2$d From be05310e7ab14c5fb3f1cbe8a850b9e9ea88198d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 2 May 2016 18:04:03 +0200 Subject: [PATCH 18/24] android: Use TextInputLayout in login dialog --- .../app/src/main/res/layout/login_dialog.xml | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/frontends/android/app/src/main/res/layout/login_dialog.xml b/src/frontends/android/app/src/main/res/layout/login_dialog.xml index 0262af0a3..adfaac5e2 100644 --- a/src/frontends/android/app/src/main/res/layout/login_dialog.xml +++ b/src/frontends/android/app/src/main/res/layout/login_dialog.xml @@ -1,9 +1,9 @@ - + android:padding="10dp" > - - - + android:layout_height="wrap_content" > - + - + + + android:layout_height="wrap_content" + android:layout_marginTop="4dp" > + + + + From c5fee223056c375c33cc32c871a86cc1966dd4a1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 30 Apr 2016 12:25:49 +0200 Subject: [PATCH 19/24] android: Make remote identity configurable in the GUI --- .../android/ui/VpnProfileDetailActivity.java | 12 +++++++++++- .../main/res/layout/profile_detail_view.xml | 18 +++++++++++++++++- .../app/src/main/res/values-de/strings.xml | 3 +++ .../app/src/main/res/values-pl/strings.xml | 3 +++ .../app/src/main/res/values-ru/strings.xml | 3 +++ .../app/src/main/res/values-ua/strings.xml | 3 +++ .../app/src/main/res/values/strings.xml | 3 +++ 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index fe523e158..6710342f0 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -90,6 +90,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity private RelativeLayout mTncNotice; private CheckBox mShowAdvanced; private ViewGroup mAdvancedSettings; + private EditText mRemoteId; + private TextInputLayoutHelper mRemoteIdWrap; private EditText mMTU; private TextInputLayoutHelper mMTUWrap; private EditText mPort; @@ -131,6 +133,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); + mRemoteId = (EditText)findViewById(R.id.remote_id); + mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap); mMTU = (EditText)findViewById(R.id.mtu); mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap); mPort = (EditText)findViewById(R.id.port); @@ -151,10 +155,12 @@ public class VpnProfileDetailActivity extends AppCompatActivity if (TextUtils.isEmpty(mGateway.getText())) { mNameWrap.setHelperText(getString(R.string.profile_name_hint)); + mRemoteIdWrap.setHelperText(getString(R.string.profile_remote_id_hint)); } else { mNameWrap.setHelperText(String.format(getString(R.string.profile_name_hint_gateway), mGateway.getText())); + mRemoteIdWrap.setHelperText(String.format(getString(R.string.profile_remote_id_hint_gateway), mGateway.getText())); } } }); @@ -384,7 +390,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity if (!show && mProfile != null) { Integer st = mProfile.getSplitTunneling(); - show = mProfile.getMTU() != null || mProfile.getPort() != null || (st != null && st != 0); + show = mProfile.getRemoteId() != null || mProfile.getMTU() != null || + mProfile.getPort() != null || (st != null && st != 0); } mShowAdvanced.setVisibility(!show ? View.VISIBLE : View.GONE); mAdvancedSettings.setVisibility(show ? View.VISIBLE : View.GONE); @@ -483,6 +490,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); + String remote_id = mRemoteId.getText().toString().trim(); + mProfile.setRemoteId(remote_id.isEmpty() ? null : remote_id); mProfile.setMTU(getInteger(mMTU)); mProfile.setPort(getInteger(mPort)); int st = 0; @@ -511,6 +520,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mVpnType = mProfile.getVpnType(); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); + mRemoteId.setText(mProfile.getRemoteId()); mMTU.setText(mProfile.getMTU() != null ? mProfile.getMTU().toString() : null); mPort.setText(mProfile.getPort() != null ? mProfile.getPort().toString() : null); mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index 4a1cc6175..737c2f9a3 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -180,10 +180,26 @@ android:text="@string/profile_advanced_label" /> + + + + + + Wählen Sie ein bestimmtes CA-Zertifikat Erweiterte Einstellungen Erweiterte Einstellungen anzeigen + Server-Identität + Standardwert ist der konfigurierte Server. Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen + Standardwert ist \"%1$s\". Eigene Werte werden explizit and den Server gesendet und während der Authentifizierung erzwungen MTU des VPN Tunnel-Device Falls der Standardwert in einem bestimmten Netzwerk nicht geeignet ist Server Port diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index c8c159537..cfd877f50 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -67,6 +67,9 @@ Wybierz określony certyfikat CA Advanced settings Show advanced settings + Server identity + Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication + Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication MTU of the VPN tunnel device In case the default value is unsuitable for a particular network Server port diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index 0550fb292..cfd8129bd 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -64,6 +64,9 @@ Выбрать CA сертификат Advanced settings Show advanced settings + Server identity + Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication + Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication MTU of the VPN tunnel device In case the default value is unsuitable for a particular network Server port diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index a52074876..85969898f 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -65,6 +65,9 @@ Вибрати спеціальний сертифікат CA Advanced settings Show advanced settings + Server identity + Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication + Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication MTU of the VPN tunnel device In case the default value is unsuitable for a particular network Server port diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index 76886cd6d..d704da062 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -67,6 +67,9 @@ Select a specific CA certificate Advanced settings Show advanced settings + Server identity + Defaults to the configured server. Custom values are explicitly sent to the server and enforced during authentication + Defaults to \"%1$s\". Custom values are explicitly sent to the server and enforced during authentication MTU of the VPN tunnel device In case the default value is unsuitable for a particular network Server port From e7a12cc862bf41d363bd70b400f9cd2cf7e51e39 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 30 Apr 2016 13:11:49 +0200 Subject: [PATCH 20/24] android: Add auto-completion to remote ID and profile name This makes it easy to explicitly use the server's IP/hostname as remote identity or use it in the profile name. --- .../android/ui/VpnProfileDetailActivity.java | 83 ++++++++++++++++++- .../main/res/layout/profile_detail_view.xml | 6 +- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 6710342f0..2af208d6f 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -31,6 +31,8 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatDialogFragment; import android.text.Editable; import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; @@ -42,10 +44,12 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.widget.MultiAutoCompleteTextView; import android.widget.RelativeLayout; import android.widget.Spinner; import android.widget.TextView; @@ -74,7 +78,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity private TrustedCertificateEntry mUserCertEntry; private VpnType mVpnType = VpnType.IKEV2_EAP; private VpnProfile mProfile; - private EditText mName; + private MultiAutoCompleteTextView mName; private TextInputLayoutHelper mNameWrap; private EditText mGateway; private TextInputLayoutHelper mGatewayWrap; @@ -90,7 +94,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity private RelativeLayout mTncNotice; private CheckBox mShowAdvanced; private ViewGroup mAdvancedSettings; - private EditText mRemoteId; + private MultiAutoCompleteTextView mRemoteId; private TextInputLayoutHelper mRemoteIdWrap; private EditText mMTU; private TextInputLayoutHelper mMTUWrap; @@ -112,7 +116,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity setContentView(R.layout.profile_detail_view); - mName = (EditText)findViewById(R.id.name); + mName = (MultiAutoCompleteTextView)findViewById(R.id.name); mNameWrap = (TextInputLayoutHelper)findViewById(R.id.name_wrap); mGateway = (EditText)findViewById(R.id.gateway); mGatewayWrap = (TextInputLayoutHelper) findViewById(R.id.gateway_wrap); @@ -133,7 +137,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mShowAdvanced = (CheckBox)findViewById(R.id.show_advanced); mAdvancedSettings = (ViewGroup)findViewById(R.id.advanced_settings); - mRemoteId = (EditText)findViewById(R.id.remote_id); + mRemoteId = (MultiAutoCompleteTextView)findViewById(R.id.remote_id); mRemoteIdWrap = (TextInputLayoutHelper) findViewById(R.id.remote_id_wrap); mMTU = (EditText)findViewById(R.id.mtu); mMTUWrap = (TextInputLayoutHelper) findViewById(R.id.mtu_wrap); @@ -142,6 +146,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity mBlockIPv4 = (CheckBox)findViewById(R.id.split_tunneling_v4); mBlockIPv6 = (CheckBox)findViewById(R.id.split_tunneling_v6); + final SpaceTokenizer spaceTokenizer = new SpaceTokenizer(); + mName.setTokenizer(spaceTokenizer); + mRemoteId.setTokenizer(spaceTokenizer); + final ArrayAdapter completeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line); + mName.setAdapter(completeAdapter); + mRemoteId.setAdapter(completeAdapter); + mGateway.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @@ -152,6 +163,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity @Override public void afterTextChanged(Editable s) { + completeAdapter.clear(); + completeAdapter.add(mGateway.getText().toString()); if (TextUtils.isEmpty(mGateway.getText())) { mNameWrap.setHelperText(getString(R.string.profile_name_hint)); @@ -695,4 +708,66 @@ public class VpnProfileDetailActivity extends AppCompatActivity }).create(); } } + + /** + * Tokenizer implementation that separates by white-space + */ + public static class SpaceTokenizer implements MultiAutoCompleteTextView.Tokenizer + { + @Override + public int findTokenStart(CharSequence text, int cursor) + { + int i = cursor; + + while (i > 0 && !Character.isWhitespace(text.charAt(i - 1))) + { + i--; + } + return i; + } + + @Override + public int findTokenEnd(CharSequence text, int cursor) + { + int i = cursor; + int len = text.length(); + + while (i < len) + { + if (Character.isWhitespace(text.charAt(i))) + { + return i; + } + else + { + i++; + } + } + return len; + } + + @Override + public CharSequence terminateToken(CharSequence text) + { + int i = text.length(); + + if (i > 0 && Character.isWhitespace(text.charAt(i - 1))) + { + return text; + } + else + { + if (text instanceof Spanned) + { + SpannableString sp = new SpannableString(text + " "); + TextUtils.copySpansFrom((Spanned) text, 0, text.length(), Object.class, sp, 0); + return sp; + } + else + { + return text + " "; + } + } + } + } } diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index 737c2f9a3..847228950 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -149,12 +149,13 @@ android:layout_marginTop="8dp" app:helper_text="@string/profile_name_hint" > - @@ -186,12 +187,13 @@ android:layout_marginTop="10dp" app:helper_text="@string/profile_remote_id_hint" > - From eb507a5a0dd13db1042ff05e5ae72c6fe0525170 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 30 Apr 2016 16:11:45 +0200 Subject: [PATCH 21/24] android: Add helper function to TrustedCertificateEntry to get subjectAltNames Duplicates (e.g. with different types) are filtered. If necessary we could later perhaps add a prefix. --- .../security/TrustedCertificateEntry.java | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java index 143741faf..5e9873d1b 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/security/TrustedCertificateEntry.java @@ -1,6 +1,6 @@ /* - * Copyright (C) 2012 Tobias Brunner - * Hochschule fuer Technik Rapperswil + * Copyright (C) 2012-2016 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil * * 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 @@ -15,10 +15,15 @@ package org.strongswan.android.security; -import java.security.cert.X509Certificate; - import android.net.http.SslCertificate; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + public class TrustedCertificateEntry implements Comparable { private final X509Certificate mCert; @@ -86,6 +91,40 @@ public class TrustedCertificateEntry implements Comparable getSubjectAltNames() + { + List list = new ArrayList<>(); + try + { + Collection> sans = mCert.getSubjectAlternativeNames(); + if (sans != null) + { + for (List san : sans) + { + switch ((Integer)san.get(0)) + { + case 1: /* rfc822Name */ + case 2: /* dnSName */ + case 7: /* iPAddress */ + list.add((String)san.get(1)); + break; + } + } + } + Collections.sort(list); + } + catch(CertificateParsingException ex) + { + ex.printStackTrace(); + } + return list; + } + /** * The alias associated with this certificate. * From cdcf754f6406b2179544c0a899588c01256e6758 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 30 Apr 2016 16:59:00 +0200 Subject: [PATCH 22/24] android: Add adapter for user ID selection --- .../adapter/CertificateIdentitiesAdapter.java | 65 +++++++++++++++++++ .../app/src/main/res/values-de/strings.xml | 3 + .../app/src/main/res/values-pl/strings.xml | 3 + .../app/src/main/res/values-ru/strings.xml | 3 + .../app/src/main/res/values-ua/strings.xml | 3 + .../app/src/main/res/values/strings.xml | 3 + 6 files changed, 80 insertions(+) create mode 100644 src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java new file mode 100644 index 000000000..c8e3df38b --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/CertificateIdentitiesAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Tobias Brunner + * HSR Hochschule fuer Technik Rapperswil + * + * 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. See . + * + * 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. + */ + +package org.strongswan.android.ui.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import org.strongswan.android.R; +import org.strongswan.android.security.TrustedCertificateEntry; + +import java.util.List; + +public class CertificateIdentitiesAdapter extends ArrayAdapter +{ + TrustedCertificateEntry mCertificate; + + public CertificateIdentitiesAdapter(Context context) + { + super(context, android.R.layout.simple_dropdown_item_1line); + extractIdentities(); + } + + /** + * Set a new certificate for this adapter. + * + * @param certificate the certificate to extract identities from (null to clear) + */ + public void setCertificate(TrustedCertificateEntry certificate) + { + mCertificate = certificate; + clear(); + extractIdentities(); + } + + private void extractIdentities() + { + if (mCertificate == null) + { + add(getContext().getString(R.string.profile_user_select_id_init)); + } + else + { + add(String.format(getContext().getString(R.string.profile_user_select_id_default), + mCertificate.getCertificate().getSubjectDN().getName())); + addAll(mCertificate.getSubjectAltNames()); + } + } +} diff --git a/src/frontends/android/app/src/main/res/values-de/strings.xml b/src/frontends/android/app/src/main/res/values-de/strings.xml index b5def060a..15ccf24fd 100644 --- a/src/frontends/android/app/src/main/res/values-de/strings.xml +++ b/src/frontends/android/app/src/main/res/values-de/strings.xml @@ -61,6 +61,9 @@ Benutzer-Zertifikat Benutzer-Zertifikat auswählen Wählen Sie ein bestimmtes Benutzer-Zertifikat + Benutzer-Identität + Wählen Sie zuerst ein Benutzer-Zertifikat + Standardwert (%1$s) CA-Zertifikat Automatisch wählen CA-Zertifikat auswählen diff --git a/src/frontends/android/app/src/main/res/values-pl/strings.xml b/src/frontends/android/app/src/main/res/values-pl/strings.xml index cfd877f50..c7aadb994 100644 --- a/src/frontends/android/app/src/main/res/values-pl/strings.xml +++ b/src/frontends/android/app/src/main/res/values-pl/strings.xml @@ -61,6 +61,9 @@ Certyfikat użytkownika Wybierz certyfikat użytkownika >Wybierz określony certyfikat użytkownika + User identity + Select a certificate first + Default (%1$s) Certyfikat CA Wybierz automatycznie Wybierz certyfikat CA diff --git a/src/frontends/android/app/src/main/res/values-ru/strings.xml b/src/frontends/android/app/src/main/res/values-ru/strings.xml index cfd8129bd..c0a6484f8 100644 --- a/src/frontends/android/app/src/main/res/values-ru/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ru/strings.xml @@ -58,6 +58,9 @@ Сертификат пользователя Выбрать сертификат пользователя Выбрать сертификат пользователя + User identity + Select a certificate first + Default (%1$s) Сертификат CA Выбрать автоматически Выбрать сертификат CA diff --git a/src/frontends/android/app/src/main/res/values-ua/strings.xml b/src/frontends/android/app/src/main/res/values-ua/strings.xml index 85969898f..a852dfdba 100644 --- a/src/frontends/android/app/src/main/res/values-ua/strings.xml +++ b/src/frontends/android/app/src/main/res/values-ua/strings.xml @@ -59,6 +59,9 @@ Сертифікат користувача Виберіть сертифікат користувача Вибрати спеціальний сертифікат користувача + User identity + Select a certificate first + Default (%1$s) Сертифікат CA Вибрати автоматично Вибрати сертифікат CA diff --git a/src/frontends/android/app/src/main/res/values/strings.xml b/src/frontends/android/app/src/main/res/values/strings.xml index d704da062..d70712181 100644 --- a/src/frontends/android/app/src/main/res/values/strings.xml +++ b/src/frontends/android/app/src/main/res/values/strings.xml @@ -61,6 +61,9 @@ User certificate Select user certificate Select a specific user certificate + User identity + Select a certificate first + Default (%1$s) CA certificate Select automatically Select CA certificate From 67fa05aa59edfdf94f54a009a4887f7bb75f0afc Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 30 Apr 2016 17:04:45 +0200 Subject: [PATCH 23/24] android: Allow selection of user identity in GUI --- .../android/ui/VpnProfileDetailActivity.java | 40 ++++++++++++++++++- .../main/res/layout/profile_detail_view.xml | 14 +++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java index 2af208d6f..29124ced7 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -61,6 +61,7 @@ import org.strongswan.android.data.VpnType; import org.strongswan.android.data.VpnType.VpnTypeFeature; import org.strongswan.android.logic.TrustedCertificateManager; import org.strongswan.android.security.TrustedCertificateEntry; +import org.strongswan.android.ui.adapter.CertificateIdentitiesAdapter; import org.strongswan.android.ui.widget.TextInputLayoutHelper; import java.security.cert.X509Certificate; @@ -75,6 +76,8 @@ public class VpnProfileDetailActivity extends AppCompatActivity private Long mId; private TrustedCertificateEntry mCertEntry; private String mUserCertLoading; + private CertificateIdentitiesAdapter mSelectUserIdAdapter; + private String mSelectedUserId; private TrustedCertificateEntry mUserCertEntry; private VpnType mVpnType = VpnType.IKEV2_EAP; private VpnProfile mProfile; @@ -89,6 +92,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity private EditText mPassword; private ViewGroup mUserCertificate; private RelativeLayout mSelectUserCert; + private Spinner mSelectUserId; private CheckBox mCheckAuto; private RelativeLayout mSelectCert; private RelativeLayout mTncNotice; @@ -130,6 +134,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mUserCertificate = (ViewGroup)findViewById(R.id.user_certificate_group); mSelectUserCert = (RelativeLayout)findViewById(R.id.select_user_certificate); + mSelectUserId = (Spinner)findViewById(R.id.select_user_id); mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); mSelectCert = (RelativeLayout)findViewById(R.id.select_certificate); @@ -205,6 +210,24 @@ public class VpnProfileDetailActivity extends AppCompatActivity }); mSelectUserCert.setOnClickListener(new SelectUserCertOnClickListener()); + mSelectUserIdAdapter = new CertificateIdentitiesAdapter(this); + mSelectUserId.setAdapter(mSelectUserIdAdapter); + mSelectUserId.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) + { + if (mUserCertEntry != null) + { /* we don't store the subject DN as it is in the reverse order and the default anyway */ + mSelectedUserId = position == 0 ? null : mSelectUserIdAdapter.getItem(position); + } + } + + @Override + public void onNothingSelected(AdapterView parent) + { + mSelectedUserId = null; + } + }); mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override @@ -265,6 +288,10 @@ public class VpnProfileDetailActivity extends AppCompatActivity { outState.putString(VpnProfileDataSource.KEY_USER_CERTIFICATE, mUserCertEntry.getAlias()); } + if (mSelectedUserId != null) + { + outState.putString(VpnProfileDataSource.KEY_LOCAL_ID, mSelectedUserId); + } if (mCertEntry != null) { outState.putString(VpnProfileDataSource.KEY_CERTIFICATE, mCertEntry.getAlias()); @@ -326,6 +353,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) { + mSelectUserId.setEnabled(false); if (mUserCertLoading != null) { ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertLoading); @@ -336,11 +364,15 @@ public class VpnProfileDetailActivity extends AppCompatActivity ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setError(null); ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(mUserCertEntry.getAlias()); ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(mUserCertEntry.getCertificate().getSubjectDN().toString()); + mSelectUserIdAdapter.setCertificate(mUserCertEntry); + mSelectUserId.setSelection(mSelectUserIdAdapter.getPosition(mSelectedUserId)); + mSelectUserId.setEnabled(true); } else { ((TextView)mSelectUserCert.findViewById(android.R.id.text1)).setText(R.string.profile_user_select_certificate_label); ((TextView)mSelectUserCert.findViewById(android.R.id.text2)).setText(R.string.profile_user_select_certificate); + mSelectUserIdAdapter.setCertificate(null); } } } @@ -500,6 +532,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity if (mVpnType.has(VpnTypeFeature.CERTIFICATE)) { mProfile.setUserCertificateAlias(mUserCertEntry.getAlias()); + mProfile.setLocalId(mSelectedUserId); } String certAlias = mCheckAuto.isChecked() ? null : mCertEntry.getAlias(); mProfile.setCertificateAlias(certAlias); @@ -520,7 +553,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity */ private void loadProfileData(Bundle savedInstanceState) { - String useralias = null, alias = null; + String useralias = null, local_id = null, alias = null; getSupportActionBar().setTitle(R.string.add_profile); if (mId != null && mId != 0) @@ -539,6 +572,7 @@ public class VpnProfileDetailActivity extends AppCompatActivity mBlockIPv4.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) != 0 : false); mBlockIPv6.setChecked(mProfile.getSplitTunneling() != null ? (mProfile.getSplitTunneling() & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) != 0 : false); useralias = mProfile.getUserCertificateAlias(); + local_id = mProfile.getLocalId(); alias = mProfile.getCertificateAlias(); getSupportActionBar().setTitle(mProfile.getName()); } @@ -553,11 +587,13 @@ public class VpnProfileDetailActivity extends AppCompatActivity mSelectVpnType.setSelection(mVpnType.ordinal()); /* check if the user selected a user certificate previously */ - useralias = savedInstanceState == null ? useralias: savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); + useralias = savedInstanceState == null ? useralias : savedInstanceState.getString(VpnProfileDataSource.KEY_USER_CERTIFICATE); + local_id = savedInstanceState == null ? local_id : savedInstanceState.getString(VpnProfileDataSource.KEY_LOCAL_ID); if (useralias != null) { UserCertificateLoader loader = new UserCertificateLoader(this, useralias); mUserCertLoading = useralias; + mSelectedUserId = local_id; loader.execute(); } diff --git a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml index 847228950..08881b38c 100644 --- a/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml +++ b/src/frontends/android/app/src/main/res/layout/profile_detail_view.xml @@ -122,6 +122,20 @@ android:id="@+id/select_user_certificate" layout="@layout/two_line_button" /> + + + + Date: Sat, 30 Apr 2016 17:14:34 +0200 Subject: [PATCH 24/24] android: Show selected user identity in profile list This also readds the colons that were removed from the labels. --- .../android/ui/adapter/VpnProfileAdapter.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java index f3bb271bc..58296a018 100644 --- a/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -63,12 +63,18 @@ public class VpnProfileAdapter extends ArrayAdapter TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); tv.setText(profile.getName()); tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); - tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); + tv.setText(getContext().getString(R.string.profile_gateway_label) + ": " + profile.getGateway()); tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); if (profile.getVpnType().has(VpnTypeFeature.USER_PASS)) { /* if the view is reused we make sure it is visible */ tv.setVisibility(View.VISIBLE); - tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + tv.setText(getContext().getString(R.string.profile_username_label) + ": " + profile.getUsername()); + } + else if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE) && + profile.getLocalId() != null) + { + tv.setVisibility(View.VISIBLE); + tv.setText(getContext().getString(R.string.profile_user_select_id_label) + ": " + profile.getLocalId()); } else { @@ -77,7 +83,7 @@ public class VpnProfileAdapter extends ArrayAdapter tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_certificate); if (profile.getVpnType().has(VpnTypeFeature.CERTIFICATE)) { - tv.setText(getContext().getString(R.string.profile_user_certificate_label) + " " + profile.getUserCertificateAlias()); + tv.setText(getContext().getString(R.string.profile_user_certificate_label) + ": " + profile.getUserCertificateAlias()); tv.setVisibility(View.VISIBLE); } else