summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2019-11-20 17:48:31 +0100
committerArne Schwabe <arne@rfc2549.org>2019-11-20 17:48:31 +0100
commit3b4ce6d5588c87bcc621a0054166c81de31d63aa (patch)
treed0333bb594e9e57c71c8d1a6e682f61fc878ecf5 /main
parent7d9cb2f62b12453c592d949974c131f5bf1ca7c1 (diff)
Implement importing profiles from Access Server
Diffstat (limited to 'main')
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java8
-rw-r--r--main/src/main/res/layout/import_as_config.xml9
-rwxr-xr-xmain/src/main/res/values/strings.xml1
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt40
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt327
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java277
6 files changed, 499 insertions, 163 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index 88336581..340853d3 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -725,9 +725,11 @@ public class ConfigParser {
}
// Parse OpenVPN Access Server extra
- Vector<String> friendlyname = meta.get("FRIENDLY_NAME");
- if (friendlyname != null && friendlyname.size() > 1)
- np.mName = friendlyname.get(1);
+ for (String as_name_directive: new String[]{"PROFILE", "FRIENDLY_NAME"}) {
+ Vector<String> friendlyname = meta.get(as_name_directive);
+ if (friendlyname != null && friendlyname.size() > 1)
+ np.mName = friendlyname.get(1);
+ }
Vector<String> ocusername = meta.get("USERNAME");
diff --git a/main/src/main/res/layout/import_as_config.xml b/main/src/main/res/layout/import_as_config.xml
index 27ef3de3..d9651db8 100644
--- a/main/src/main/res/layout/import_as_config.xml
+++ b/main/src/main/res/layout/import_as_config.xml
@@ -7,6 +7,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
+ android:padding="20dp"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -19,7 +20,7 @@
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:hint="@string/as_servername"
- android:inputType="textPassword" />
+ android:inputType="textUri" />
<EditText
android:id="@+id/username"
@@ -53,10 +54,4 @@
android:layout_marginBottom="16dp"
android:text="@string/request_autologin" />
-
- <Button
- android:id="@+id/import_config"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/import_config" />
</LinearLayout> \ No newline at end of file
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index fb0c1936..cd770c14 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -495,5 +495,6 @@
<string name="install_keychain">Install new certificate</string>
<string name="as_servername">AS servername</string>
<string name="request_autologin">Request autologin profile</string>
+ <string name="import_from_as">Import Profile from Access Server</string>
</resources>
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
index b2a76f3d..b91628a2 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
@@ -37,6 +37,7 @@ import de.blinkt.openvpn.views.FileSelectLayout
import de.blinkt.openvpn.views.FileSelectLayout.FileSelectCallback
import java.io.*
import java.net.URLDecoder
+import java.nio.charset.StandardCharsets
import java.util.*
class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener {
@@ -511,7 +512,7 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
// Read in the bytes
var offset = 0
- var bytesRead:Int
+ var bytesRead: Int
do {
bytesRead = input.read(bytes, offset, bytes.size - offset)
offset += bytesRead
@@ -604,11 +605,13 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
}
private fun doImportIntent(intent: Intent) {
- val data = intent.data
- if (intent.action.equals(IMPORT_PROFILE_DATA))
- if (data != null) {
- mSourceUri = data
- doImportUri(data)
+ if (intent.action.equals(IMPORT_PROFILE_DATA)) {
+ val data = intent.getStringExtra(Intent.EXTRA_TEXT)
+
+ if (data != null) {
+ startImportTask(Uri.fromParts("inline", "inlinetext", null),
+ "imported profiles from AS", data);
+ }
}
}
@@ -650,12 +653,12 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
possibleName = possibleName.replace(".conf", "")
}
- startImportTask(data, possibleName)
+ startImportTask(data, possibleName, "")
}
- private fun startImportTask(data: Uri, possibleName: String?) {
+ private fun startImportTask(data: Uri, possibleName: String?, inlineData:String) {
mImportTask = object : AsyncTask<Void, Void, Int>() {
private var mProgress: ProgressBar? = null
@@ -666,10 +669,16 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
override fun doInBackground(vararg params: Void): Int? {
try {
- val `is` = contentResolver.openInputStream(data)
-
- doImport(`is`)
- `is`!!.close()
+ var inputStream:InputStream?
+ if (data.scheme.equals("inline")) {
+ inputStream = inlineData.byteInputStream()
+ } else {
+ inputStream = contentResolver.openInputStream(data)
+ }
+
+ if (inputStream != null) {
+ doImport(inputStream)
+ }
if (mResult == null)
return -3
} catch (se: IOException) {
@@ -738,10 +747,10 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
mLogLayout.addView(view, mLogLayout.childCount - 1)
}
- private fun doImport(`is`: InputStream?) {
+ private fun doImport(inputStream: InputStream) {
val cp = ConfigParser()
try {
- val isr = InputStreamReader(`is`!!)
+ val isr = InputStreamReader(inputStream)
cp.parseConfig(isr)
mResult = cp.convertProfile()
@@ -757,9 +766,10 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
}
mResult = null
-
+ inputStream.close()
}
+
private fun displayWarnings() {
if (mResult!!.mUseCustomConfig) {
log(R.string.import_warning_custom_options)
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt
new file mode 100644
index 00000000..f501c0cf
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2012-2019 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.net.TrafficStats
+import android.os.Bundle
+import android.util.Base64
+import android.util.Base64.NO_WRAP
+import android.util.Log
+import android.widget.CheckBox
+import android.widget.EditText
+import android.widget.Toast
+import androidx.fragment.app.DialogFragment
+import de.blinkt.openvpn.R
+import de.blinkt.openvpn.activities.ConfigConverter
+import de.blinkt.openvpn.core.Preferences
+import okhttp3.*
+import okhttp3.internal.tls.OkHostnameVerifier
+import org.jetbrains.anko.doAsync
+import org.jetbrains.anko.runOnUiThread
+import java.io.IOException
+import java.lang.Exception
+import java.security.MessageDigest
+import java.security.cert.CertPathValidatorException
+import java.security.cert.CertificateException
+import java.security.cert.X509Certificate
+import javax.net.ssl.*
+
+class BasicAuthInterceptor(user: String, password: String) : Interceptor {
+
+ private val credentials: String
+
+ init {
+ this.credentials = Credentials.basic(user, password)
+ }
+
+ @Throws(IOException::class)
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ val authenticatedRequest = request.newBuilder()
+ .header("Authorization", credentials).build()
+ return chain.proceed(authenticatedRequest)
+ }
+
+}
+
+
+fun getCompositeSSLSocketFactory(certPin: CertificatePinner, hostname: String): SSLSocketFactory {
+ val trustPinnedCerts = arrayOf<TrustManager>(object : X509TrustManager {
+ override fun getAcceptedIssuers(): Array<X509Certificate> {
+ return emptyArray()
+ }
+
+ @Throws(CertificateException::class)
+ override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
+ throw CertificateException("Why would we check client certificates?!")
+ }
+
+ @Throws(CertificateException::class)
+ override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
+ certPin.check(hostname, chain.toList())
+ }
+ })
+
+ // Install the all-trusting trust manager
+ val sslContext = SSLContext.getInstance("TLS")
+ sslContext.init(null, trustPinnedCerts, java.security.SecureRandom())
+ // Create an ssl socket factory with our all-trusting manager
+
+ return sslContext.socketFactory
+
+}
+
+class ImportASConfig : DialogFragment() {
+ private lateinit var asUseAutlogin: CheckBox
+ private lateinit var asServername: EditText
+ private lateinit var asUsername: EditText
+ private lateinit var asPassword: EditText
+
+
+ internal fun getHostNameVerifier(prefs: SharedPreferences): HostnameVerifier {
+ val pinnedHostnames: Set<String> = prefs.getStringSet("pinnedHosts", emptySet())!!
+
+ val mapping = mutableMapOf<String, String?>()
+
+ pinnedHostnames.forEach { ph ->
+ mapping[ph] = prefs.getString("pin-${ph}", "")
+ }
+
+ val defaultVerifier = OkHostnameVerifier.INSTANCE;
+ val pinHostVerifier = object : HostnameVerifier {
+ override fun verify(hostname: String?, session: SSLSession?): Boolean {
+ val unverifiedHandshake = Handshake.get(session)
+ val cert = unverifiedHandshake.peerCertificates()[0] as X509Certificate
+ val hostPin = CertificatePinner.pin(cert)
+
+ if (mapping.containsKey(hostname) && mapping[hostname] == hostPin)
+ return true
+ else
+ return defaultVerifier.verify(hostname, session)
+ }
+
+ }
+ return pinHostVerifier
+ }
+
+ internal fun buildHttpClient(c: Context, user: String, password: String, hostname: String): OkHttpClient {
+
+ // TODO: HACK
+ val THREAD_ID = 10000;
+ TrafficStats.setThreadStatsTag(THREAD_ID);
+
+ val prefs = c.getSharedPreferences("pinnedCerts", Context.MODE_PRIVATE)
+ val pinnedHosts: Set<String> = prefs.getStringSet("pinnedHosts", emptySet())!!
+
+ val okHttpClient = OkHttpClient.Builder()
+ .addInterceptor(BasicAuthInterceptor(user, password))
+
+ /* Rely on system certificates if we do not have the host pinned */
+ if (pinnedHosts.contains(hostname)) {
+ val cpb = CertificatePinner.Builder()
+
+ pinnedHosts.forEach { ph ->
+ cpb.add(ph, prefs.getString("pin-${ph}", ""))
+ }
+
+
+ val certPinner = cpb.build()
+ getCompositeSSLSocketFactory(certPinner, hostname).let {
+ okHttpClient.sslSocketFactory(it)
+ }
+ //okHttpClient.certificatePinner(certPinner)
+ }
+
+ okHttpClient.hostnameVerifier(getHostNameVerifier(prefs))
+
+ val client = okHttpClient.build()
+ return client
+
+ }
+
+ /**
+ * @param fp Fingerprint in sha 256 format
+ */
+ internal fun addPinnedCert(c: Context, host: String, fp: String) {
+ val prefs = c.getSharedPreferences("pinnedCerts", Context.MODE_PRIVATE)
+ val pedit = prefs.edit()
+ val pinnedHosts: MutableSet<String> = prefs.getStringSet("pinnedHosts", mutableSetOf<String>())!!
+
+ pinnedHosts.add(host)
+
+ pedit.putString("pin-${host}", "sha256/${fp}")
+
+ pedit.putStringSet("pinnedHosts", pinnedHosts)
+
+ pedit.apply()
+ }
+
+ fun fetchProfile(c: Context, asUri: HttpUrl, user: String, password: String): Response? {
+
+
+ val httpClient = buildHttpClient(c, user, password, asUri.host() ?: "")
+
+ val request = Request.Builder()
+ .url(asUri)
+ .build()
+
+ val response = httpClient.newCall(request).execute()
+
+ return response
+
+ }
+
+ private fun getAsUrl(url: String, autologin: Boolean): HttpUrl {
+ var asurl = url
+ if (!asurl.startsWith("http"))
+ asurl = "https://" + asurl
+
+ if (autologin)
+ asurl += "/rest/GetAutologin"
+ else
+ asurl += "/rest/GetUserlogin"
+
+ val asUri = HttpUrl.parse(asurl)
+ return asUri
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val inflater = requireActivity().layoutInflater
+ val view = inflater.inflate(R.layout.import_as_config, null);
+
+ val builder = AlertDialog.Builder(requireContext())
+
+ builder.setView(view)
+
+
+ builder.setTitle(R.string.import_from_as)
+
+ asServername = view.findViewById(R.id.as_servername)
+ asUsername = view.findViewById(R.id.username)
+ asPassword = view.findViewById(R.id.password)
+ asUseAutlogin = view.findViewById(R.id.request_autologin)
+
+ builder.setPositiveButton(R.string.import_config, null)
+ builder.setNegativeButton(android.R.string.cancel) { _, _ -> }
+
+ val dialog = builder.create()
+
+ dialog.setOnShowListener() { d2 ->
+ val d: AlertDialog = d2 as AlertDialog
+
+ d.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener()
+ { _ ->
+ doAsImport()
+ }
+ }
+ return dialog
+ }
+
+ internal fun doAsImport() {
+ val ab = AlertDialog.Builder(requireContext())
+ ab.setTitle("Downloading profile")
+ ab.setMessage("Please wait")
+ val pleaseWait = ab.show()
+ Toast.makeText(context, "Downloading profile", Toast.LENGTH_LONG).show()
+ val asProfileUri = getAsUrl(asServername.text.toString(), asUseAutlogin.isChecked)
+
+ doAsync {
+ var e: Exception? = null
+ try {
+ val response = fetchProfile(requireContext(), asProfileUri,
+ asUsername.text.toString(), asPassword.text.toString())
+
+ if (response == null) {
+ throw Exception("No Response from Server")
+ } else if (response.isSuccessful) {
+ val profile = response.body().string()
+ activity?.runOnUiThread() {
+ pleaseWait?.dismiss()
+ val startImport = Intent(activity, ConfigConverter::class.java)
+ startImport.action = ConfigConverter.IMPORT_PROFILE_DATA
+ startImport.putExtra(Intent.EXTRA_TEXT, profile)
+ startActivity(startImport)
+ dismiss()
+ }
+ } else {
+ throw Exception("Invalid Response from server: \n${response.code()} ${response.message()} \n\n ${response.body().string()}")
+ }
+
+ } catch (ce: SSLHandshakeException) {
+ // Find out if we are in the non trust path
+ if (ce.cause is CertificateException && ce.cause != null) {
+ val certExp: CertificateException = (ce.cause as CertificateException)
+ if (certExp.cause is CertPathValidatorException && certExp.cause != null) {
+ val caPathExp: CertPathValidatorException = certExp.cause as CertPathValidatorException
+ if (caPathExp.certPath.type.equals("X.509") && caPathExp.certPath.certificates.size > 0) {
+ val firstCert: X509Certificate = (caPathExp.certPath.certificates[0] as X509Certificate)
+
+ val fpBytes = MessageDigest.getInstance("SHA-256").digest(firstCert.publicKey.encoded)
+ val fp = Base64.encodeToString(fpBytes, NO_WRAP)
+
+
+
+ Log.i("OpenVPN", "Found cert with FP ${fp}: ${firstCert.subjectDN}")
+ requireContext().runOnUiThread {
+
+ pleaseWait?.dismiss()
+
+ AlertDialog.Builder(requireContext())
+ .setTitle("Untrusted certificate found")
+ .setMessage(firstCert.toString())
+ .setPositiveButton("Trust") { _, _ -> addPinnedCert(requireContext(), asProfileUri.host(), fp) }
+ .setNegativeButton("Do not trust", null)
+ .show()
+ Toast.makeText(requireContext(), "Found cert with FP ${fp}: ${firstCert.subjectDN.toString()}", Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ } else {
+ e = ce
+ }
+ } catch (ge: Exception) {
+ e = ge
+ }
+ if (e != null) {
+ activity?.runOnUiThread() {
+ pleaseWait?.dismiss()
+ AlertDialog.Builder(requireContext())
+ .setTitle("Import failed")
+ .setMessage("Error: " + e.localizedMessage)
+ .setPositiveButton(android.R.string.ok, null)
+ .show()
+ }
+ }
+ }
+ }
+
+
+ override fun onResume() {
+ super.onResume()
+ asServername.setText(Preferences.getDefaultSharedPreferences(activity).getString("as-hostname", ""))
+ asUsername.setText(Preferences.getDefaultSharedPreferences(activity).getString("as-username", ""))
+ }
+
+ override fun onPause() {
+ super.onPause()
+ val prefs = Preferences.getDefaultSharedPreferences(activity)
+ prefs.edit().putString("as-hostname", asServername.text.toString()).apply()
+ prefs.edit().putString("as-username", asUsername.text.toString()).apply()
+ }
+
+ companion object {
+ @JvmStatic
+ fun newInstance(): ImportASConfig {
+ return ImportASConfig();
+ }
+ }
+
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java
index eaccd201..eb81e62e 100644
--- a/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012-2016 Arne Schwabe
+ * Copyright (c) 2012-2019 Arne Schwabe
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
*/
@@ -19,7 +19,9 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
+
import androidx.annotation.RequiresApi;
+import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.ListFragment;
import android.text.Html;
@@ -62,22 +64,24 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
public final static int RESULT_VPN_DELETED = Activity.RESULT_FIRST_USER;
public final static int RESULT_VPN_DUPLICATE = Activity.RESULT_FIRST_USER + 1;
-
+ // Shortcut version is increased to refresh all shortcuts
+ final static int SHORTCUT_VERSION = 1;
private static final int MENU_ADD_PROFILE = Menu.FIRST;
-
private static final int START_VPN_CONFIG = 92;
private static final int SELECT_PROFILE = 43;
private static final int IMPORT_PROFILE = 231;
private static final int FILE_PICKER_RESULT_KITKAT = 392;
-
private static final int MENU_IMPORT_PROFILE = Menu.FIRST + 1;
private static final int MENU_CHANGE_SORTING = Menu.FIRST + 2;
+ private static final int MENU_IMPORT_AS = Menu.FIRST + 3;
private static final String PREF_SORT_BY_LRU = "sortProfilesByLRU";
+ protected VpnProfile mEditProfile = null;
private String mLastStatusMessage;
+ private ArrayAdapter<VpnProfile> mArrayadapter;
@Override
public void updateState(String state, String logmessage, final int localizedResId, ConnectionStatus level) {
- getActivity().runOnUiThread(() -> {
+ requireActivity().runOnUiThread(() -> {
mLastStatusMessage = VpnStatus.getLastCleanLogMessage(getActivity());
mArrayadapter.notifyDataSetChanged();
});
@@ -87,51 +91,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
public void setConnectedVPN(String uuid) {
}
- private class VPNArrayAdapter extends ArrayAdapter<VpnProfile> {
-
- public VPNArrayAdapter(Context context, int resource,
- int textViewResourceId) {
- super(context, resource, textViewResourceId);
- }
-
- @Override
- public View getView(final int position, View convertView, ViewGroup parent) {
- View v = super.getView(position, convertView, parent);
-
- final VpnProfile profile = (VpnProfile) getListAdapter().getItem(position);
-
- View titleview = v.findViewById(R.id.vpn_list_item_left);
- titleview.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- startOrStopVPN(profile);
- }
- });
-
- View settingsview = v.findViewById(R.id.quickedit_settings);
- settingsview.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- editVPN(profile);
-
- }
- });
-
- TextView subtitle = (TextView) v.findViewById(R.id.vpn_item_subtitle);
- if (profile.getUUIDString().equals(VpnStatus.getLastConnectedVPNProfile())) {
- subtitle.setText(mLastStatusMessage);
- subtitle.setVisibility(View.VISIBLE);
- } else {
- subtitle.setText("");
- subtitle.setVisibility(View.GONE);
- }
-
-
- return v;
- }
- }
-
private void startOrStopVPN(VpnProfile profile) {
if (VpnStatus.isVPNActive() && profile.getUUIDString().equals(VpnStatus.getLastConnectedVPNProfile())) {
Intent disconnectVPN = new Intent(getActivity(), DisconnectVPN.class);
@@ -141,22 +100,12 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
}
}
-
- private ArrayAdapter<VpnProfile> mArrayadapter;
-
- protected VpnProfile mEditProfile = null;
-
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
-
- // Shortcut version is increased to refresh all shortcuts
- final static int SHORTCUT_VERSION = 1;
-
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
void updateDynamicShortcuts() {
PersistableBundle versionExtras = new PersistableBundle();
@@ -267,28 +216,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
.build();
}
- class MiniImageGetter implements ImageGetter {
-
-
- @Override
- public Drawable getDrawable(String source) {
- Drawable d = null;
- if ("ic_menu_add".equals(source))
- d = getActivity().getResources().getDrawable(R.drawable.ic_menu_add_grey);
- else if ("ic_menu_archive".equals(source))
- d = getActivity().getResources().getDrawable(R.drawable.ic_menu_import_grey);
-
-
- if (d != null) {
- d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
- return d;
- } else {
- return null;
- }
- }
- }
-
-
@Override
public void onResume() {
super.onResume();
@@ -334,55 +261,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
setListAdapter();
}
- static class VpnProfileNameComparator implements Comparator<VpnProfile> {
-
- @Override
- public int compare(VpnProfile lhs, VpnProfile rhs) {
- if (lhs == rhs)
- // Catches also both null
- return 0;
-
- if (lhs == null)
- return -1;
- if (rhs == null)
- return 1;
-
- if (lhs.mName == null)
- return -1;
- if (rhs.mName == null)
- return 1;
-
- return lhs.mName.compareTo(rhs.mName);
- }
-
- }
-
- static class VpnProfileLRUComparator implements Comparator<VpnProfile> {
-
- VpnProfileNameComparator nameComparator = new VpnProfileNameComparator();
-
- @Override
- public int compare(VpnProfile lhs, VpnProfile rhs) {
- if (lhs == rhs)
- // Catches also both null
- return 0;
-
- if (lhs == null)
- return -1;
- if (rhs == null)
- return 1;
-
- // Copied from Long.compare
- if (lhs.mLastUsed > rhs.mLastUsed)
- return -1;
- if (lhs.mLastUsed < rhs.mLastUsed)
- return 1;
- else
- return nameComparator.compare(lhs, rhs);
- }
- }
-
-
private void setListAdapter() {
if (mArrayadapter == null) {
mArrayadapter = new VPNArrayAdapter(getActivity(), R.layout.vpn_list_item, R.id.vpn_item_title);
@@ -408,7 +286,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
mArrayadapter.notifyDataSetChanged();
}
-
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(0, MENU_ADD_PROFILE, 0, R.string.menu_add_profile)
@@ -429,8 +306,13 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
.setTitleCondensed(getString(R.string.sort))
.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- }
+ menu.add(0, MENU_IMPORT_AS, 0, R.string.import_from_as)
+ .setIcon(R.drawable.ic_menu_import)
+ .setAlphabeticShortcut('p')
+ .setTitleCondensed("Import AS")
+ .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -442,13 +324,21 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
return startImportConfigFilePicker();
} else if (itemId == MENU_CHANGE_SORTING) {
return changeSorting();
+ } else if (itemId == MENU_IMPORT_AS) {
+ return startASProfileImport();
} else {
return super.onOptionsItemSelected(item);
}
}
+ private boolean startASProfileImport() {
+ ImportASConfig asImportFrag = ImportASConfig.newInstance();
+ asImportFrag.show(requireFragmentManager(), "dialog");
+ return true;
+ }
+
private boolean changeSorting() {
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(getActivity());
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(requireActivity());
boolean oldValue = prefs.getBoolean(PREF_SORT_BY_LRU, false);
SharedPreferences.Editor prefsedit = prefs.edit();
if (oldValue) {
@@ -477,7 +367,7 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
private boolean startImportConfigFilePicker() {
boolean startOldFileDialog = true;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !Utils.alwaysUseOldFileChooser(getActivity() ))
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !Utils.alwaysUseOldFileChooser(getActivity()))
startOldFileDialog = !startFilePicker();
if (startOldFileDialog)
@@ -504,7 +394,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
startActivityForResult(intent, SELECT_PROFILE);
}
-
private void onAddOrDuplicateProfile(final VpnProfile mCopyProfile) {
Context context = getActivity();
if (context != null) {
@@ -560,7 +449,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
return ProfileManager.getInstance(getActivity());
}
-
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@@ -612,7 +500,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
startActivityForResult(startImport, IMPORT_PROFILE);
}
-
private void editVPN(VpnProfile profile) {
mEditProfile = profile;
Intent vprefintent = new Intent(getActivity(), VPNPreferences.class)
@@ -630,4 +517,118 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn
intent.setAction(Intent.ACTION_MAIN);
startActivity(intent);
}
+
+ static class VpnProfileNameComparator implements Comparator<VpnProfile> {
+
+ @Override
+ public int compare(VpnProfile lhs, VpnProfile rhs) {
+ if (lhs == rhs)
+ // Catches also both null
+ return 0;
+
+ if (lhs == null)
+ return -1;
+ if (rhs == null)
+ return 1;
+
+ if (lhs.mName == null)
+ return -1;
+ if (rhs.mName == null)
+ return 1;
+
+ return lhs.mName.compareTo(rhs.mName);
+ }
+
+ }
+
+ static class VpnProfileLRUComparator implements Comparator<VpnProfile> {
+
+ VpnProfileNameComparator nameComparator = new VpnProfileNameComparator();
+
+ @Override
+ public int compare(VpnProfile lhs, VpnProfile rhs) {
+ if (lhs == rhs)
+ // Catches also both null
+ return 0;
+
+ if (lhs == null)
+ return -1;
+ if (rhs == null)
+ return 1;
+
+ // Copied from Long.compare
+ if (lhs.mLastUsed > rhs.mLastUsed)
+ return -1;
+ if (lhs.mLastUsed < rhs.mLastUsed)
+ return 1;
+ else
+ return nameComparator.compare(lhs, rhs);
+ }
+ }
+
+ private class VPNArrayAdapter extends ArrayAdapter<VpnProfile> {
+
+ public VPNArrayAdapter(Context context, int resource,
+ int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+ }
+
+ @Override
+ public View getView(final int position, View convertView, ViewGroup parent) {
+ View v = super.getView(position, convertView, parent);
+
+ final VpnProfile profile = (VpnProfile) getListAdapter().getItem(position);
+
+ View titleview = v.findViewById(R.id.vpn_list_item_left);
+ titleview.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startOrStopVPN(profile);
+ }
+ });
+
+ View settingsview = v.findViewById(R.id.quickedit_settings);
+ settingsview.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ editVPN(profile);
+
+ }
+ });
+
+ TextView subtitle = (TextView) v.findViewById(R.id.vpn_item_subtitle);
+ if (profile.getUUIDString().equals(VpnStatus.getLastConnectedVPNProfile())) {
+ subtitle.setText(mLastStatusMessage);
+ subtitle.setVisibility(View.VISIBLE);
+ } else {
+ subtitle.setText("");
+ subtitle.setVisibility(View.GONE);
+ }
+
+
+ return v;
+ }
+ }
+
+ class MiniImageGetter implements ImageGetter {
+
+
+ @Override
+ public Drawable getDrawable(String source) {
+ Drawable d = null;
+ if ("ic_menu_add".equals(source))
+ d = getActivity().getResources().getDrawable(R.drawable.ic_menu_add_grey);
+ else if ("ic_menu_archive".equals(source))
+ d = getActivity().getResources().getDrawable(R.drawable.ic_menu_import_grey);
+
+
+ if (d != null) {
+ d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+ return d;
+ } else {
+ return null;
+ }
+ }
+ }
}