summaryrefslogtreecommitdiff
path: root/main/src/ui/java/de/blinkt/openvpn/activities
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/ui/java/de/blinkt/openvpn/activities')
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.java43
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.java847
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/CreateShortcuts.java159
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/FileSelect.java257
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/LogWindow.java37
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java131
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/OpenSSLSpeed.java193
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.java244
8 files changed, 1911 insertions, 0 deletions
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.java b/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.java
new file mode 100644
index 00000000..7258d8d6
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012-2015 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.RestrictionsManager;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.view.Window;
+import de.blinkt.openvpn.api.AppRestrictions;
+
+public class BaseActivity extends Activity {
+ private boolean isAndroidTV() {
+ final UiModeManager uiModeManager = (UiModeManager) getSystemService(Activity.UI_MODE_SERVICE);
+ return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (isAndroidTV()) {
+ requestWindowFeature(Window.FEATURE_OPTIONS_PANEL);
+ }
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.java b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.java
new file mode 100644
index 00000000..38b47b5a
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.java
@@ -0,0 +1,847 @@
+
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.OpenableColumns;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Pair;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ConfigParser;
+import de.blinkt.openvpn.core.ConfigParser.ConfigParseError;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.fragments.Utils;
+import de.blinkt.openvpn.views.FileSelectLayout;
+
+import static de.blinkt.openvpn.views.FileSelectLayout.FileSelectCallback;
+
+public class ConfigConverter extends BaseActivity implements FileSelectCallback, View.OnClickListener {
+
+ public static final String IMPORT_PROFILE = "de.blinkt.openvpn.IMPORT_PROFILE";
+ private static final int RESULT_INSTALLPKCS12 = 7;
+ private static final int CHOOSE_FILE_OFFSET = 1000;
+ public static final String VPNPROFILE = "vpnProfile";
+ private static final int PERMISSION_REQUEST_EMBED_FILES = 37231;
+ private static final int PERMISSION_REQUEST_READ_URL = PERMISSION_REQUEST_EMBED_FILES + 1;
+
+ private VpnProfile mResult;
+
+ private transient List<String> mPathsegments;
+
+ private String mAliasName = null;
+
+
+ private Map<Utils.FileType, FileSelectLayout> fileSelectMap = new HashMap<>();
+ private String mEmbeddedPwFile;
+ private Vector<String> mLogEntries = new Vector<>();
+ private Uri mSourceUri;
+ private EditText mProfilename;
+ private AsyncTask<Void, Void, Integer> mImportTask;
+ private LinearLayout mLogLayout;
+ private TextView mProfilenameLabel;
+
+ @Override
+ public void onClick(View v) {
+ if (v.getId() == R.id.fab_save)
+ userActionSaveProfile();
+ if (v.getId() == R.id.permssion_hint && Build.VERSION.SDK_INT == Build.VERSION_CODES.M)
+ doRequestSDCardPermission(PERMISSION_REQUEST_EMBED_FILES);
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void doRequestSDCardPermission(int requestCode) {
+ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, requestCode);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ // Permission declined, do nothing
+ if (grantResults.length == 0 || grantResults[0] == PackageManager.PERMISSION_DENIED)
+ return;
+
+ // Reset file select dialogs
+ findViewById(R.id.files_missing_hint).setVisibility(View.GONE);
+ findViewById(R.id.permssion_hint).setVisibility(View.GONE);
+ LinearLayout fileroot = (LinearLayout) findViewById(R.id.config_convert_root);
+ for (int i = 0; i < fileroot.getChildCount(); ) {
+ if (fileroot.getChildAt(i) instanceof FileSelectLayout)
+ fileroot.removeViewAt(i);
+ else
+ i++;
+ }
+
+ if (requestCode == PERMISSION_REQUEST_EMBED_FILES)
+ embedFiles(null);
+
+ else if (requestCode == PERMISSION_REQUEST_READ_URL) {
+ if (mSourceUri != null)
+ doImportUri(mSourceUri);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.cancel) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ } else if (item.getItemId() == R.id.ok) {
+ return userActionSaveProfile();
+ }
+
+ return super.onOptionsItemSelected(item);
+
+ }
+
+ private boolean userActionSaveProfile() {
+ if (mResult == null) {
+ log(R.string.import_config_error);
+ Toast.makeText(this, R.string.import_config_error, Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ mResult.mName = mProfilename.getText().toString();
+ ProfileManager vpl = ProfileManager.getInstance(this);
+ if (vpl.getProfileByName(mResult.mName) != null) {
+ mProfilename.setError(getString(R.string.duplicate_profile_name));
+ return true;
+ }
+
+ Intent in = installPKCS12();
+
+ if (in != null)
+ startActivityForResult(in, RESULT_INSTALLPKCS12);
+ else
+ saveProfile();
+
+ return true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mResult != null)
+ outState.putSerializable(VPNPROFILE, mResult);
+ outState.putString("mAliasName", mAliasName);
+
+
+ String[] logentries = mLogEntries.toArray(new String[mLogEntries.size()]);
+
+ outState.putStringArray("logentries", logentries);
+
+ int[] fileselects = new int[fileSelectMap.size()];
+ int k = 0;
+ for (Utils.FileType key : fileSelectMap.keySet()) {
+ fileselects[k] = key.getValue();
+ k++;
+ }
+ outState.putIntArray("fileselects", fileselects);
+ outState.putString("pwfile", mEmbeddedPwFile);
+ outState.putParcelable("mSourceUri", mSourceUri);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent result) {
+ if (requestCode == RESULT_INSTALLPKCS12 && resultCode == Activity.RESULT_OK) {
+ showCertDialog();
+ }
+
+ if (resultCode == Activity.RESULT_OK && requestCode >= CHOOSE_FILE_OFFSET) {
+ Utils.FileType type = Utils.FileType.getFileTypeByValue(requestCode - CHOOSE_FILE_OFFSET);
+
+
+ FileSelectLayout fs = fileSelectMap.get(type);
+ fs.parseResponse(result, this);
+
+ String data = fs.getData();
+
+ switch (type) {
+ case USERPW_FILE:
+ mEmbeddedPwFile = data;
+ break;
+ case PKCS12:
+ mResult.mPKCS12Filename = data;
+ break;
+ case TLS_AUTH_FILE:
+ mResult.mTLSAuthFilename = data;
+ break;
+ case CA_CERTIFICATE:
+ mResult.mCaFilename = data;
+ break;
+ case CLIENT_CERTIFICATE:
+ mResult.mClientCertFilename = data;
+ break;
+ case KEYFILE:
+ mResult.mClientKeyFilename = data;
+ break;
+ case CRL_FILE:
+ mResult.mCrlFilename = data;
+ break;
+ default:
+ throw new RuntimeException("Type is wrong somehow?");
+ }
+ }
+
+ super.onActivityResult(requestCode, resultCode, result);
+ }
+
+ private void saveProfile() {
+ Intent result = new Intent();
+ ProfileManager vpl = ProfileManager.getInstance(this);
+
+ if (!TextUtils.isEmpty(mEmbeddedPwFile))
+ ConfigParser.useEmbbedUserAuth(mResult, mEmbeddedPwFile);
+
+ vpl.addProfile(mResult);
+ vpl.saveProfile(this, mResult);
+ vpl.saveProfileList(this);
+ result.putExtra(VpnProfile.EXTRA_PROFILEUUID, mResult.getUUID().toString());
+ setResult(Activity.RESULT_OK, result);
+ finish();
+ }
+
+ public void showCertDialog() {
+ try {
+ //noinspection WrongConstant
+ KeyChain.choosePrivateKeyAlias(this,
+ new KeyChainAliasCallback() {
+
+ public void alias(String alias) {
+ // Credential alias selected. Remember the alias selection for future use.
+ mResult.mAlias = alias;
+ saveProfile();
+ }
+
+
+ },
+ new String[]{"RSA", "EC"}, // List of acceptable key types. null for any
+ null, // issuer, null for any
+ mResult.mServerName, // host name of server requesting the cert, null if unavailable
+ -1, // port of server requesting the cert, -1 if unavailable
+ mAliasName); // alias to preselect, null if unavailable
+ } catch (ActivityNotFoundException anf) {
+ Builder ab = new AlertDialog.Builder(this);
+ ab.setTitle(R.string.broken_image_cert_title);
+ ab.setMessage(R.string.broken_image_cert);
+ ab.setPositiveButton(android.R.string.ok, null);
+ ab.show();
+ }
+ }
+
+
+ private Intent installPKCS12() {
+
+ if (!((CheckBox) findViewById(R.id.importpkcs12)).isChecked()) {
+ setAuthTypeToEmbeddedPKCS12();
+ return null;
+
+ }
+ String pkcs12datastr = mResult.mPKCS12Filename;
+ if (VpnProfile.isEmbedded(pkcs12datastr)) {
+ Intent inkeyIntent = KeyChain.createInstallIntent();
+
+ pkcs12datastr = VpnProfile.getEmbeddedContent(pkcs12datastr);
+
+
+ byte[] pkcs12data = Base64.decode(pkcs12datastr, Base64.DEFAULT);
+
+
+ inkeyIntent.putExtra(KeyChain.EXTRA_PKCS12, pkcs12data);
+
+ if (mAliasName.equals(""))
+ mAliasName = null;
+
+ if (mAliasName != null) {
+ inkeyIntent.putExtra(KeyChain.EXTRA_NAME, mAliasName);
+ }
+ return inkeyIntent;
+
+ }
+ return null;
+ }
+
+
+ private void setAuthTypeToEmbeddedPKCS12() {
+ if (VpnProfile.isEmbedded(mResult.mPKCS12Filename)) {
+ if (mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE)
+ mResult.mAuthenticationType = VpnProfile.TYPE_USERPASS_PKCS12;
+
+ if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE)
+ mResult.mAuthenticationType = VpnProfile.TYPE_PKCS12;
+
+ }
+ }
+
+
+ private String getUniqueProfileName(String possibleName) {
+
+ int i = 0;
+
+ ProfileManager vpl = ProfileManager.getInstance(this);
+
+ String newname = possibleName;
+
+ // Default to
+ if (mResult.mName != null && !ConfigParser.CONVERTED_PROFILE.equals(mResult.mName))
+ newname = mResult.mName;
+
+ while (newname == null || vpl.getProfileByName(newname) != null) {
+ i++;
+ if (i == 1)
+ newname = getString(R.string.converted_profile);
+ else
+ newname = getString(R.string.converted_profile_i, i);
+ }
+
+ return newname;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.import_menu, menu);
+ return true;
+ }
+
+ private String embedFile(String filename, Utils.FileType type, boolean onlyFindFileAndNullonNotFound) {
+ if (filename == null)
+ return null;
+
+ // Already embedded, nothing to do
+ if (VpnProfile.isEmbedded(filename))
+ return filename;
+
+ File possibleFile = findFile(filename, type);
+ if (possibleFile == null)
+ if (onlyFindFileAndNullonNotFound)
+ return null;
+ else
+ return filename;
+ else if (onlyFindFileAndNullonNotFound)
+ return possibleFile.getAbsolutePath();
+ else
+ return readFileContent(possibleFile, type == Utils.FileType.PKCS12);
+
+ }
+
+
+ private Pair<Integer, String> getFileDialogInfo(Utils.FileType type) {
+ int titleRes = 0;
+ String value = null;
+ switch (type) {
+ case KEYFILE:
+ titleRes = R.string.client_key_title;
+ if (mResult != null)
+ value = mResult.mClientKeyFilename;
+ break;
+ case CLIENT_CERTIFICATE:
+ titleRes = R.string.client_certificate_title;
+ if (mResult != null)
+ value = mResult.mClientCertFilename;
+ break;
+ case CA_CERTIFICATE:
+ titleRes = R.string.ca_title;
+ if (mResult != null)
+ value = mResult.mCaFilename;
+ break;
+ case TLS_AUTH_FILE:
+ titleRes = R.string.tls_auth_file;
+ if (mResult != null)
+ value = mResult.mTLSAuthFilename;
+ break;
+ case PKCS12:
+ titleRes = R.string.client_pkcs12_title;
+ if (mResult != null)
+ value = mResult.mPKCS12Filename;
+ break;
+
+ case USERPW_FILE:
+ titleRes = R.string.userpw_file;
+ value = mEmbeddedPwFile;
+ break;
+
+ case CRL_FILE:
+ titleRes = R.string.crl_file;
+ value = mResult.mCrlFilename;
+ break;
+ }
+
+ return Pair.create(titleRes, value);
+
+ }
+
+ private File findFile(String filename, Utils.FileType fileType) {
+ File foundfile = findFileRaw(filename);
+
+ if (foundfile == null && filename != null && !filename.equals("")) {
+ log(R.string.import_could_not_open, filename);
+ }
+ fileSelectMap.put(fileType, null);
+
+ return foundfile;
+ }
+
+ private void addMissingFileDialogs()
+ {
+ for (Map.Entry<Utils.FileType, FileSelectLayout> item: fileSelectMap.entrySet()) {
+ if (item.getValue()==null)
+ addFileSelectDialog(item.getKey());
+ }
+ }
+
+ private void addFileSelectDialog(Utils.FileType type) {
+
+ Pair<Integer, String> fileDialogInfo = getFileDialogInfo(type);
+
+ boolean isCert = type == Utils.FileType.CA_CERTIFICATE || type == Utils.FileType.CLIENT_CERTIFICATE;
+ FileSelectLayout fl = new FileSelectLayout(this, getString(fileDialogInfo.first), isCert, false);
+ fileSelectMap.put(type, fl);
+ fl.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ ((LinearLayout) findViewById(R.id.config_convert_root)).addView(fl, 2);
+ findViewById(R.id.files_missing_hint).setVisibility(View.VISIBLE);
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M)
+ checkPermission();
+
+ fl.setData(fileDialogInfo.second, this);
+ int i = getFileLayoutOffset(type);
+ fl.setCaller(this, i, type);
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void checkPermission() {
+ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ findViewById(R.id.permssion_hint).setVisibility(View.VISIBLE);
+ findViewById(R.id.permssion_hint).setOnClickListener(this);
+ }
+ }
+
+ private int getFileLayoutOffset(Utils.FileType type) {
+ return CHOOSE_FILE_OFFSET + type.getValue();
+ }
+
+
+ private File findFileRaw(String filename) {
+ if (filename == null || filename.equals(""))
+ return null;
+
+ // Try diffent path relative to /mnt/sdcard
+ File sdcard = Environment.getExternalStorageDirectory();
+ File root = new File("/");
+
+ HashSet<File> dirlist = new HashSet<>();
+
+ for (int i = mPathsegments.size() - 1; i >= 0; i--) {
+ String path = "";
+ for (int j = 0; j <= i; j++) {
+ path += "/" + mPathsegments.get(j);
+ }
+ // Do a little hackish dance for the Android File Importer
+ // /document/primary:ovpn/openvpn-imt.conf
+
+
+ if (path.indexOf(':') != -1 && path.lastIndexOf('/') > path.indexOf(':')) {
+ String possibleDir = path.substring(path.indexOf(':') + 1, path.length());
+ // Unquote chars in the path
+ try {
+ possibleDir = URLDecoder.decode(possibleDir, "UTF-8");
+ } catch (UnsupportedEncodingException ignored) {}
+
+ possibleDir = possibleDir.substring(0, possibleDir.lastIndexOf('/'));
+
+
+
+
+ dirlist.add(new File(sdcard, possibleDir));
+
+ }
+ dirlist.add(new File(path));
+
+
+ }
+ dirlist.add(sdcard);
+ dirlist.add(root);
+
+
+ String[] fileparts = filename.split("/");
+ for (File rootdir : dirlist) {
+ String suffix = "";
+ for (int i = fileparts.length - 1; i >= 0; i--) {
+ if (i == fileparts.length - 1)
+ suffix = fileparts[i];
+ else
+ suffix = fileparts[i] + "/" + suffix;
+
+ File possibleFile = new File(rootdir, suffix);
+ if (possibleFile.canRead())
+ return possibleFile;
+
+ }
+ }
+ return null;
+ }
+
+ String readFileContent(File possibleFile, boolean base64encode) {
+ byte[] filedata;
+ try {
+ filedata = readBytesFromFile(possibleFile);
+ } catch (IOException e) {
+ log(e.getLocalizedMessage());
+ return null;
+ }
+
+ String data;
+ if (base64encode) {
+ data = Base64.encodeToString(filedata, Base64.DEFAULT);
+ } else {
+ data = new String(filedata);
+
+ }
+
+ return VpnProfile.DISPLAYNAME_TAG + possibleFile.getName() + VpnProfile.INLINE_TAG + data;
+
+ }
+
+
+ private byte[] readBytesFromFile(File file) throws IOException {
+ InputStream input = new FileInputStream(file);
+
+ long len = file.length();
+ if (len > VpnProfile.MAX_EMBED_FILE_SIZE)
+ throw new IOException("File size of file to import too large.");
+
+ // Create the byte array to hold the data
+ byte[] bytes = new byte[(int) len];
+
+ // Read in the bytes
+ int offset = 0;
+ int bytesRead;
+ while (offset < bytes.length
+ && (bytesRead = input.read(bytes, offset, bytes.length - offset)) >= 0) {
+ offset += bytesRead;
+ }
+
+ input.close();
+ return bytes;
+ }
+
+ void embedFiles(ConfigParser cp) {
+ // This where I would like to have a c++ style
+ // void embedFile(std::string & option)
+
+ if (mResult.mPKCS12Filename != null) {
+ File pkcs12file = findFileRaw(mResult.mPKCS12Filename);
+ if (pkcs12file != null) {
+ mAliasName = pkcs12file.getName().replace(".p12", "");
+ } else {
+ mAliasName = "Imported PKCS12";
+ }
+ }
+
+
+ mResult.mCaFilename = embedFile(mResult.mCaFilename, Utils.FileType.CA_CERTIFICATE, false);
+ mResult.mClientCertFilename = embedFile(mResult.mClientCertFilename, Utils.FileType.CLIENT_CERTIFICATE, false);
+ mResult.mClientKeyFilename = embedFile(mResult.mClientKeyFilename, Utils.FileType.KEYFILE, false);
+ mResult.mTLSAuthFilename = embedFile(mResult.mTLSAuthFilename, Utils.FileType.TLS_AUTH_FILE, false);
+ mResult.mPKCS12Filename = embedFile(mResult.mPKCS12Filename, Utils.FileType.PKCS12, false);
+ mResult.mCrlFilename = embedFile(mResult.mCrlFilename, Utils.FileType.CRL_FILE, true);
+ if (cp != null) {
+ mEmbeddedPwFile = cp.getAuthUserPassFile();
+ mEmbeddedPwFile = embedFile(cp.getAuthUserPassFile(), Utils.FileType.USERPW_FILE, false);
+ }
+
+ }
+
+ private void updateFileSelectDialogs() {
+ for (Map.Entry<Utils.FileType, FileSelectLayout> fl : fileSelectMap.entrySet()) {
+ fl.getValue().setData(getFileDialogInfo(fl.getKey()).second, this);
+ }
+ }
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.config_converter);
+
+ ImageButton fab_button = (ImageButton) findViewById(R.id.fab_save);
+ if (fab_button != null) {
+ fab_button.setOnClickListener(this);
+ findViewById(R.id.fab_footerspace).setVisibility(View.VISIBLE);
+ }
+
+ mLogLayout = (LinearLayout) findViewById(R.id.config_convert_root);
+
+
+ mProfilename = (EditText) findViewById(R.id.profilename);
+ mProfilenameLabel = (TextView) findViewById(R.id.profilename_label);
+
+ if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) {
+ mResult = (VpnProfile) savedInstanceState.getSerializable(VPNPROFILE);
+ mAliasName = savedInstanceState.getString("mAliasName");
+ mEmbeddedPwFile = savedInstanceState.getString("pwfile");
+ mSourceUri = savedInstanceState.getParcelable("mSourceUri");
+ mProfilename.setText(mResult.mName);
+
+ if (savedInstanceState.containsKey("logentries")) {
+ //noinspection ConstantConditions
+ for (String logItem : savedInstanceState.getStringArray("logentries"))
+ log(logItem);
+ }
+ if (savedInstanceState.containsKey("fileselects")) {
+ //noinspection ConstantConditions
+ for (int k : savedInstanceState.getIntArray("fileselects")) {
+ addFileSelectDialog(Utils.FileType.getFileTypeByValue(k));
+ }
+ }
+ return;
+ }
+
+
+ final android.content.Intent intent = getIntent();
+
+ if (intent != null) {
+ doImportIntent(intent);
+
+ // We parsed the intent, relay on saved instance for restoring
+ setIntent(null);
+ }
+
+
+ }
+
+ private void doImportIntent(Intent intent) {
+ final Uri data = intent.getData();
+ if (data != null) {
+ mSourceUri = data;
+ doImportUri(data);
+ }
+ }
+
+ private void doImportUri(Uri data) {
+ //log(R.string.import_experimental);
+ log(R.string.importing_config, data.toString());
+ String possibleName = null;
+ if ((data.getScheme() != null && data.getScheme().equals("file")) ||
+ (data.getLastPathSegment() != null &&
+ (data.getLastPathSegment().endsWith(".ovpn") ||
+ data.getLastPathSegment().endsWith(".conf")))
+ ) {
+ possibleName = data.getLastPathSegment();
+ if (possibleName.lastIndexOf('/') != -1)
+ possibleName = possibleName.substring(possibleName.lastIndexOf('/') + 1);
+
+ }
+
+ mPathsegments = data.getPathSegments();
+
+ Cursor cursor = getContentResolver().query(data, null, null, null, null);
+
+ try {
+
+ if (cursor != null && cursor.moveToFirst()) {
+ int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+
+ if (columnIndex != -1) {
+ String displayName = cursor.getString(columnIndex);
+ if (displayName != null)
+ possibleName = displayName;
+ }
+ columnIndex = cursor.getColumnIndex("mime_type");
+ if (columnIndex != -1) {
+ log("Mime type: " + cursor.getString(columnIndex));
+ }
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ if (possibleName != null) {
+ possibleName = possibleName.replace(".ovpn", "");
+ possibleName = possibleName.replace(".conf", "");
+ }
+
+ startImportTask(data, possibleName);
+
+
+ }
+
+ private void startImportTask(final Uri data, final String possibleName) {
+ mImportTask = new AsyncTask<Void, Void, Integer>() {
+ private ProgressBar mProgress;
+
+ @Override
+ protected void onPreExecute() {
+ mProgress = new ProgressBar(ConfigConverter.this);
+ addViewToLog(mProgress);
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ try {
+ InputStream is = getContentResolver().openInputStream(data);
+
+ doImport(is);
+ is.close();
+ if (mResult==null)
+ return -3;
+ } catch (IOException| SecurityException se)
+
+ {
+ log(R.string.import_content_resolve_error + ":" + se.getLocalizedMessage());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ checkMarschmallowFileImportError(data);
+ return -2;
+ }
+
+ return 0;
+ }
+
+ @Override
+ protected void onPostExecute(Integer errorCode) {
+ mLogLayout.removeView(mProgress);
+ addMissingFileDialogs();
+ updateFileSelectDialogs();
+
+ if (errorCode == 0) {
+ displayWarnings();
+ mResult.mName = getUniqueProfileName(possibleName);
+ mProfilename.setVisibility(View.VISIBLE);
+ mProfilenameLabel.setVisibility(View.VISIBLE);
+ mProfilename.setText(mResult.getName());
+
+ log(R.string.import_done);
+ }
+ }
+ }.execute();
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void checkMarschmallowFileImportError(Uri data) {
+ // Permission already granted, not the source of the error
+ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
+ return;
+
+ // We got a file:/// URL and have no permission to read it. Technically an error of the calling app since
+ // it makes an assumption about other apps being able to read the url but well ...
+ if (data != null && "file".equals(data.getScheme()))
+ doRequestSDCardPermission(PERMISSION_REQUEST_READ_URL);
+
+ }
+
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ }
+
+ private void log(final String logmessage) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ TextView tv = new TextView(ConfigConverter.this);
+ mLogEntries.add(logmessage);
+ tv.setText(logmessage);
+
+ addViewToLog(tv);
+ }
+ });
+ }
+
+ private void addViewToLog(View view) {
+ mLogLayout.addView(view, mLogLayout.getChildCount() - 1);
+ }
+
+ private void doImport(InputStream is) {
+ ConfigParser cp = new ConfigParser();
+ try {
+ InputStreamReader isr = new InputStreamReader(is);
+
+ cp.parseConfig(isr);
+ mResult = cp.convertProfile();
+ embedFiles(cp);
+ return;
+
+ } catch (IOException | ConfigParseError e) {
+ log(R.string.error_reading_config_file);
+ log(e.getLocalizedMessage());
+ }
+ mResult = null;
+
+ }
+
+ private void displayWarnings() {
+ if (mResult.mUseCustomConfig) {
+ log(R.string.import_warning_custom_options);
+ String copt = mResult.mCustomConfigOptions;
+ if (copt.startsWith("#")) {
+ int until = copt.indexOf('\n');
+ copt = copt.substring(until + 1);
+ }
+
+ log(copt);
+ }
+
+ if (mResult.mAuthenticationType == VpnProfile.TYPE_KEYSTORE ||
+ mResult.mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) {
+ findViewById(R.id.importpkcs12).setVisibility(View.VISIBLE);
+ }
+
+ }
+
+ private void log(int ressourceId, Object... formatArgs) {
+ log(getString(ressourceId, formatArgs));
+ }
+
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/CreateShortcuts.java b/main/src/ui/java/de/blinkt/openvpn/activities/CreateShortcuts.java
new file mode 100644
index 00000000..e1cb8862
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/CreateShortcuts.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import de.blinkt.openvpn.LaunchVPN;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ProfileManager;
+
+import java.util.Collection;
+import java.util.Vector;
+
+/**
+ * This Activity actually handles two stages of a launcher shortcut's life cycle.
+ *
+ * 1. Your application offers to provide shortcuts to the launcher. When
+ * the user installs a shortcut, an activity within your application
+ * generates the actual shortcut and returns it to the launcher, where it
+ * is shown to the user as an icon.
+ *
+ * 2. Any time the user clicks on an installed shortcut, an intent is sent.
+ * Typically this would then be handled as necessary by an activity within
+ * your application.
+ *
+ * We handle stage 1 (creating a shortcut) by simply sending back the information (in the form
+ * of an {@link android.content.Intent} that the launcher will use to create the shortcut.
+ *
+ * You can also implement this in an interactive way, by having your activity actually present
+ * UI for the user to select the specific nature of the shortcut, such as a contact, picture, URL,
+ * media item, or action.
+ *
+ * We handle stage 2 (responding to a shortcut) in this sample by simply displaying the contents
+ * of the incoming {@link android.content.Intent}.
+ *
+ * In a real application, you would probably use the shortcut intent to display specific content
+ * or start a particular operation.
+ */
+public class CreateShortcuts extends ListActivity implements OnItemClickListener {
+
+
+ private static final int START_VPN_PROFILE= 70;
+
+
+ private ProfileManager mPM;
+ private VpnProfile mSelectedProfile;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mPM =ProfileManager.getInstance(this);
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ // Resolve the intent
+
+ createListView();
+ }
+
+ private void createListView() {
+ ListView lv = getListView();
+ //lv.setTextFilterEnabled(true);
+
+ Collection<VpnProfile> vpnList = mPM.getProfiles();
+
+ Vector<String> vpnNames=new Vector<String>();
+ for (VpnProfile vpnProfile : vpnList) {
+ vpnNames.add(vpnProfile.mName);
+ }
+
+
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,vpnNames);
+ lv.setAdapter(adapter);
+
+ lv.setOnItemClickListener(this);
+ }
+
+ /**
+ * This function creates a shortcut and returns it to the caller. There are actually two
+ * intents that you will send back.
+ *
+ * The first intent serves as a container for the shortcut and is returned to the launcher by
+ * setResult(). This intent must contain three fields:
+ *
+ * <ul>
+ * <li>{@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.</li>
+ * <li>{@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with
+ * the shortcut.</li>
+ * <li>{@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a
+ * bitmap, <i>or</i> {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as
+ * a drawable resource.</li>
+ * </ul>
+ *
+ * If you use a simple drawable resource, note that you must wrapper it using
+ * {@link android.content.Intent.ShortcutIconResource}, as shown below. This is required so
+ * that the launcher can access resources that are stored in your application's .apk file. If
+ * you return a bitmap, such as a thumbnail, you can simply put the bitmap into the extras
+ * bundle using {@link android.content.Intent#EXTRA_SHORTCUT_ICON}.
+ *
+ * The shortcut intent can be any intent that you wish the launcher to send, when the user
+ * clicks on the shortcut. Typically this will be {@link android.content.Intent#ACTION_VIEW}
+ * with an appropriate Uri for your content, but any Intent will work here as long as it
+ * triggers the desired action within your Activity.
+ * @param profile
+ */
+ private void setupShortcut(VpnProfile profile) {
+ // First, set up the shortcut intent. For this example, we simply create an intent that
+ // will bring us directly back to this activity. A more typical implementation would use a
+ // data Uri in order to display a more specific result, or a custom action in order to
+ // launch a specific operation.
+
+ Intent shortcutIntent = new Intent(Intent.ACTION_MAIN);
+ shortcutIntent.setClass(this, LaunchVPN.class);
+ shortcutIntent.putExtra(LaunchVPN.EXTRA_KEY,profile.getUUID().toString());
+
+ // Then, set up the container intent (the response to the caller)
+
+ Intent intent = new Intent();
+ intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, profile.getName());
+ Parcelable iconResource = Intent.ShortcutIconResource.fromContext(
+ this, R.mipmap.ic_launcher);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+
+ // Now, return the result to the launcher
+
+ setResult(RESULT_OK, intent);
+ }
+
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ String profileName = ((TextView) view).getText().toString();
+
+ VpnProfile profile = mPM.getProfileByName(profileName);
+
+ setupShortcut(profile);
+ finish();
+ }
+
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/FileSelect.java b/main/src/ui/java/de/blinkt/openvpn/activities/FileSelect.java
new file mode 100644
index 00000000..80a134a9
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/FileSelect.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Base64;
+import android.widget.Toast;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.fragments.FileSelectionFragment;
+import de.blinkt.openvpn.fragments.InlineFileTab;
+
+public class FileSelect extends BaseActivity {
+ public static final String RESULT_DATA = "RESULT_PATH";
+ public static final String START_DATA = "START_DATA";
+ public static final String WINDOW_TITLE = "WINDOW_TILE";
+ public static final String NO_INLINE_SELECTION = "de.blinkt.openvpn.NO_INLINE_SELECTION";
+ public static final String SHOW_CLEAR_BUTTON = "de.blinkt.openvpn.SHOW_CLEAR_BUTTON";
+ public static final String DO_BASE64_ENCODE = "de.blinkt.openvpn.BASE64ENCODE";
+ private static final int PERMISSION_REQUEST = 23621;
+
+ private FileSelectionFragment mFSFragment;
+ private InlineFileTab mInlineFragment;
+ private String mData;
+ private Tab inlineFileTab;
+ private Tab fileExplorerTab;
+ private boolean mNoInline;
+ private boolean mShowClear;
+ private boolean mBase64Encode;
+
+
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.file_dialog);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ checkPermission();
+
+ mData = getIntent().getStringExtra(START_DATA);
+ if(mData==null)
+ mData=Environment.getExternalStorageDirectory().getPath();
+
+ String title = getIntent().getStringExtra(WINDOW_TITLE);
+ int titleId = getIntent().getIntExtra(WINDOW_TITLE, 0);
+ if(titleId!=0)
+ title =getString(titleId);
+ if(title!=null)
+ setTitle(title);
+
+ mNoInline = getIntent().getBooleanExtra(NO_INLINE_SELECTION, false);
+ mShowClear = getIntent().getBooleanExtra(SHOW_CLEAR_BUTTON, false);
+ mBase64Encode = getIntent().getBooleanExtra(DO_BASE64_ENCODE, false);
+
+ ActionBar bar = getActionBar();
+ bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ fileExplorerTab = bar.newTab().setText(R.string.file_explorer_tab);
+ inlineFileTab = bar.newTab().setText(R.string.inline_file_tab);
+
+ mFSFragment = new FileSelectionFragment();
+ fileExplorerTab.setTabListener(new MyTabsListener<FileSelectionFragment>(this, mFSFragment));
+ bar.addTab(fileExplorerTab);
+
+ if(!mNoInline) {
+ mInlineFragment = new InlineFileTab();
+ inlineFileTab.setTabListener(new MyTabsListener<InlineFileTab>(this, mInlineFragment));
+ bar.addTab(inlineFileTab);
+ } else {
+ mFSFragment.setNoInLine();
+ }
+
+
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private void checkPermission() {
+ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_REQUEST);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ if (mNoInline) {
+ setResult(RESULT_CANCELED);
+ finish();
+ } else {
+ if (fileExplorerTab!=null)
+ getActionBar().removeTab(fileExplorerTab);
+ }
+ } else {
+ mFSFragment.refresh();
+ }
+ }
+
+ public boolean showClear() {
+ if(mData == null || mData.equals(""))
+ return false;
+ else
+ return mShowClear;
+ }
+
+ protected class MyTabsListener<T extends Fragment> implements ActionBar.TabListener
+ {
+ private Fragment mFragment;
+ private boolean mAdded=false;
+
+ public MyTabsListener( Activity activity, Fragment fragment){
+ this.mFragment = fragment;
+ }
+
+ public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ // Check if the fragment is already initialized
+ if (!mAdded) {
+ // If not, instantiate and add it to the activity
+ ft.add(android.R.id.content, mFragment);
+ mAdded =true;
+ } else {
+ // If it exists, simply attach it in order to show it
+ ft.attach(mFragment);
+ }
+ }
+
+ @Override
+ public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ ft.detach(mFragment);
+ }
+
+ @Override
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+
+ }
+ }
+
+ public void importFile(String path) {
+ File ifile = new File(path);
+ Exception fe = null;
+ try {
+
+ String data = "";
+
+ byte[] fileData = readBytesFromFile(ifile) ;
+ if(mBase64Encode)
+ data += Base64.encodeToString(fileData, Base64.DEFAULT);
+ else
+ data += new String(fileData);
+
+ mData =data;
+
+ /*
+ mInlineFragment.setData(data);
+ getActionBar().selectTab(inlineFileTab); */
+ saveInlineData(ifile.getName(), data);
+ } catch (IOException e) {
+ fe =e;
+ }
+ if(fe!=null) {
+ Builder ab = new AlertDialog.Builder(this);
+ ab.setTitle(R.string.error_importing_file);
+ ab.setMessage(getString(R.string.import_error_message) + "\n" + fe.getLocalizedMessage());
+ ab.setPositiveButton(android.R.string.ok, null);
+ ab.show();
+ }
+ }
+
+ static private byte[] readBytesFromFile(File file) throws IOException {
+ InputStream input = new FileInputStream(file);
+
+ long len= file.length();
+ if (len > VpnProfile.MAX_EMBED_FILE_SIZE)
+ throw new IOException("selected file size too big to embed into profile");
+
+ // Create the byte array to hold the data
+ byte[] bytes = new byte[(int) len];
+
+ // Read in the bytes
+ int offset = 0;
+ int bytesRead = 0;
+ while (offset < bytes.length
+ && (bytesRead=input.read(bytes, offset, bytes.length-offset)) >= 0) {
+ offset += bytesRead;
+ }
+
+ input.close();
+ return bytes;
+ }
+
+
+ public void setFile(String path) {
+ Intent intent = new Intent();
+ intent.putExtra(RESULT_DATA, path);
+ setResult(Activity.RESULT_OK,intent);
+ finish();
+ }
+
+ public String getSelectPath() {
+ if(VpnProfile.isEmbedded(mData))
+ return mData;
+ else
+ return Environment.getExternalStorageDirectory().getPath();
+ }
+
+ public CharSequence getInlineData() {
+ if(VpnProfile.isEmbedded(mData))
+ return VpnProfile.getEmbeddedContent(mData);
+ else
+ return "";
+ }
+
+ public void clearData() {
+ Intent intent = new Intent();
+ intent.putExtra(RESULT_DATA, (String)null);
+ setResult(Activity.RESULT_OK,intent);
+ finish();
+
+ }
+
+ public void saveInlineData(String fileName, String string) {
+ Intent intent = new Intent();
+
+ if(fileName==null)
+ intent.putExtra(RESULT_DATA, VpnProfile.INLINE_TAG + string);
+ else
+ intent.putExtra(RESULT_DATA,VpnProfile.DISPLAYNAME_TAG + fileName + VpnProfile.INLINE_TAG + string);
+ setResult(Activity.RESULT_OK, intent);
+ finish();
+
+ }
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/LogWindow.java b/main/src/ui/java/de/blinkt/openvpn/activities/LogWindow.java
new file mode 100644
index 00000000..db70eca9
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/LogWindow.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.fragments.LogFragment;
+
+/**
+ * Created by arne on 13.10.13.
+ */
+public class LogWindow extends BaseActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.log_window);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ getFragmentManager().beginTransaction()
+ .add(R.id.container, new LogFragment())
+ .commit();
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java
new file mode 100644
index 00000000..f7c46d01
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.support.v4n.view.ViewPager;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.fragments.AboutFragment;
+import de.blinkt.openvpn.fragments.FaqFragment;
+import de.blinkt.openvpn.fragments.GeneralSettings;
+import de.blinkt.openvpn.fragments.GraphFragment;
+import de.blinkt.openvpn.fragments.LogFragment;
+import de.blinkt.openvpn.fragments.SendDumpFragment;
+import de.blinkt.openvpn.fragments.VPNProfileList;
+import de.blinkt.openvpn.views.ScreenSlidePagerAdapter;
+import de.blinkt.openvpn.views.SlidingTabLayout;
+import de.blinkt.openvpn.views.TabBarView;
+
+
+public class MainActivity extends BaseActivity {
+
+ private ViewPager mPager;
+ private ScreenSlidePagerAdapter mPagerAdapter;
+ private SlidingTabLayout mSlidingTabLayout;
+ private TabBarView mTabs;
+
+ protected void onCreate(android.os.Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main_activity);
+
+
+ // Instantiate a ViewPager and a PagerAdapter.
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager(), this);
+
+ /* Toolbar and slider should have the same elevation */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ disableToolbarElevation();
+ }
+
+
+ mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList.class);
+ mPagerAdapter.addTab(R.string.graph, GraphFragment.class);
+
+ mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings.class);
+ mPagerAdapter.addTab(R.string.faq, FaqFragment.class);
+
+ if (SendDumpFragment.getLastestDump(this) != null) {
+ mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment.class);
+ }
+
+
+ if (isDirectToTV())
+ mPagerAdapter.addTab(R.string.openvpn_log, LogFragment.class);
+
+ mPagerAdapter.addTab(R.string.about, AboutFragment.class);
+ mPager.setAdapter(mPagerAdapter);
+
+ mTabs = (TabBarView) findViewById(R.id.sliding_tabs);
+ mTabs.setViewPager(mPager);
+ }
+
+ private static final String FEATURE_TELEVISION = "android.hardware.type.television";
+ private static final String FEATURE_LEANBACK = "android.software.leanback";
+
+ private boolean isDirectToTV() {
+ return(getPackageManager().hasSystemFeature(FEATURE_TELEVISION)
+ || getPackageManager().hasSystemFeature(FEATURE_LEANBACK));
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void disableToolbarElevation() {
+ ActionBar toolbar = getActionBar();
+ toolbar.setElevation(0);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (getIntent()!=null) {
+ String page = getIntent().getStringExtra("PAGE");
+ if ("graph".equals(page)) {
+ mPager.setCurrentItem(1);
+ }
+ setIntent(null);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main_menu,menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId()==R.id.show_log){
+ Intent showLog = new Intent(this, LogWindow.class);
+ startActivity(showLog);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ System.out.println(data);
+
+
+ }
+
+
+
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/OpenSSLSpeed.java b/main/src/ui/java/de/blinkt/openvpn/activities/OpenSSLSpeed.java
new file mode 100644
index 00000000..4720dd60
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/OpenSSLSpeed.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2012-2017 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.app.Activity;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.Locale;
+import java.util.Vector;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.core.NativeUtils;
+import de.blinkt.openvpn.core.OpenVPNService;
+
+public class OpenSSLSpeed extends Activity {
+
+ private static SpeeedTest runTestAlgorithms;
+ private EditText mCipher;
+ private SpeedArrayAdapter mAdapter;
+ private ListView mListView;
+
+
+ static class SpeedArrayAdapter extends ArrayAdapter<SpeedResult> {
+
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+
+ public SpeedArrayAdapter(@NonNull Context context) {
+ super(context, 0);
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+
+ }
+
+ class ViewHolder {
+ TextView ciphername;
+ TextView blocksize;
+ TextView blocksInTime;
+ TextView speed;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View view, @NonNull ViewGroup parent) {
+ SpeedResult res = getItem(position);
+ if (view == null) {
+ view = mInflater.inflate(R.layout.speedviewitem, parent, false);
+ ViewHolder holder = new ViewHolder();
+ holder.ciphername = view.findViewById(R.id.ciphername);
+ holder.speed = view.findViewById(R.id.speed);
+ holder.blocksize = view.findViewById(R.id.blocksize);
+ holder.blocksInTime = view.findViewById(R.id.blocksintime);
+ view.setTag(holder);
+ }
+
+ ViewHolder holder = (ViewHolder) view.getTag();
+
+ double total = res.count * res.length;
+ String size = OpenVPNService.humanReadableByteCount((long) res.length, false, mContext.getResources());
+
+ holder.blocksize.setText(size);
+ holder.ciphername.setText(res.algorithm);
+
+ if (res.failed) {
+ holder.blocksInTime.setText(R.string.openssl_error);
+ holder.speed.setText("-");
+ } else if (res.running) {
+ holder.blocksInTime.setText(R.string.running_test);
+ holder.speed.setText("-");
+ } else {
+ String totalBytes = OpenVPNService.humanReadableByteCount((long) total, false, mContext.getResources());
+ // TODO: Fix localisation here
+ String blockPerSec = OpenVPNService.humanReadableByteCount((long) (total / res.time), false, mContext.getResources()) + "/s";
+ holder.speed.setText(blockPerSec);
+ holder.blocksInTime.setText(String.format(Locale.ENGLISH, "%d blocks (%s) in %2.1fs", (long) res.count, totalBytes, res.time));
+ }
+
+ return view;
+
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.openssl_speed);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ findViewById(R.id.testSpecific).setOnClickListener((view) -> {
+ runAlgorithms(mCipher.getText().toString());
+ });
+ mCipher = (EditText) findViewById(R.id.ciphername);
+
+ mListView = findViewById(R.id.results);
+
+ mAdapter = new SpeedArrayAdapter(this);
+ mListView.setAdapter(mAdapter);
+
+ }
+
+ private void runAlgorithms(String algorithms) {
+ if (runTestAlgorithms != null)
+ runTestAlgorithms.cancel(true);
+ runTestAlgorithms = new SpeeedTest();
+ runTestAlgorithms.execute(algorithms.split(" "));
+ }
+
+
+ static class SpeedResult {
+ String algorithm;
+ boolean failed = false;
+
+ double count;
+ double time;
+ int length;
+ public boolean running=true;
+
+ SpeedResult(String algorithm) {
+ this.algorithm = algorithm;
+ }
+ }
+
+
+ private class SpeeedTest extends AsyncTask<String, SpeedResult, SpeedResult[]> {
+
+
+ private boolean mCancel = false;
+
+ @Override
+ protected SpeedResult[] doInBackground(String... strings) {
+ Vector<SpeedResult> mResult = new Vector<>();
+
+ for (String algorithm : strings) {
+
+ // Skip 16b and 16k as they are not relevevant for VPN
+ for (int i = 1; i < NativeUtils.openSSLlengths.length -1 && !mCancel; i++) {
+ SpeedResult result = new SpeedResult(algorithm);
+ result.length = NativeUtils.openSSLlengths[i];
+ mResult.add(result);
+ publishProgress(result);
+ double[] resi = NativeUtils.getOpenSSLSpeed(algorithm, i);
+ if (resi == null) {
+ result.failed = true;
+ } else {
+ result.count = resi[1];
+ result.time = resi[2];
+ }
+ result.running = false;
+ publishProgress(result);
+ }
+ }
+
+ return mResult.toArray(new SpeedResult[mResult.size()]);
+
+ }
+
+ @Override
+ protected void onProgressUpdate(SpeedResult... values) {
+ for (SpeedResult r : values) {
+ if (r.running)
+ mAdapter.add(r);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected void onPostExecute(SpeedResult[] speedResult) {
+
+ }
+
+ @Override
+ protected void onCancelled(SpeedResult[] speedResults) {
+ mCancel = true;
+ }
+ }
+
+
+}
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.java b/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.java
new file mode 100644
index 00000000..06f1f7b7
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.activities;
+
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.v4n.view.ViewPager;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import android.widget.Toast;
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ProfileManager;
+import de.blinkt.openvpn.fragments.Settings_Allowed_Apps;
+import de.blinkt.openvpn.fragments.Settings_Authentication;
+import de.blinkt.openvpn.fragments.Settings_Basic;
+import de.blinkt.openvpn.fragments.Settings_Connections;
+import de.blinkt.openvpn.fragments.Settings_IP;
+import de.blinkt.openvpn.fragments.Settings_Obscure;
+import de.blinkt.openvpn.fragments.Settings_Routing;
+import de.blinkt.openvpn.fragments.Settings_UserEditable;
+import de.blinkt.openvpn.fragments.ShowConfigFragment;
+import de.blinkt.openvpn.fragments.VPNProfileList;
+import de.blinkt.openvpn.views.ScreenSlidePagerAdapter;
+import de.blinkt.openvpn.views.TabBarView;
+
+
+public class VPNPreferences extends BaseActivity {
+
+ static final Class validFragments[] = new Class[] {
+ Settings_Authentication.class, Settings_Basic.class, Settings_IP.class,
+ Settings_Obscure.class, Settings_Routing.class, ShowConfigFragment.class,
+ Settings_Connections.class, Settings_Allowed_Apps.class
+ };
+
+ private String mProfileUUID;
+ private VpnProfile mProfile;
+ private ViewPager mPager;
+ private ScreenSlidePagerAdapter mPagerAdapter;
+
+ public VPNPreferences() {
+ super();
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ protected boolean isValidFragment(String fragmentName) {
+ for (Class c: validFragments)
+ if (c.getName().equals(fragmentName))
+ return true;
+ return false;
+
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ outState.putString(getIntent().getStringExtra(getPackageName() + ".profileUUID"),mProfileUUID);
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getProfile();
+ // When a profile is deleted from a category fragment in hadset mod we need to finish
+ // this activity as well when returning
+ if (mProfile==null || mProfile.profileDeleted) {
+ setResult(VPNProfileList.RESULT_VPN_DELETED);
+ finish();
+ }
+ if (mProfile.mTemporaryProfile)
+ {
+ Toast.makeText(this, "Temporary profiles cannot be edited", Toast.LENGTH_LONG);
+ finish();
+ }
+ }
+
+ private void getProfile() {
+ Intent intent = getIntent();
+
+ if(intent!=null) {
+ String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID");
+ if(profileUUID==null) {
+ Bundle initialArguments = getIntent().getBundleExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ profileUUID = initialArguments.getString(getPackageName() + ".profileUUID");
+ }
+ if(profileUUID!=null){
+
+ mProfileUUID = profileUUID;
+ mProfile = ProfileManager.get(this, mProfileUUID);
+
+ }
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ mProfileUUID = getIntent().getStringExtra(getPackageName() + ".profileUUID");
+ if(savedInstanceState!=null){
+ String savedUUID = savedInstanceState.getString(getPackageName() + ".profileUUID");
+ if(savedUUID!=null)
+ mProfileUUID=savedUUID;
+ }
+
+ mProfile = ProfileManager.get(this,mProfileUUID);
+ if(mProfile!=null) {
+ setTitle(getString(R.string.edit_profile_title, mProfile.getName()));
+ }
+ super.onCreate(savedInstanceState);
+
+
+ setContentView(R.layout.main_activity);
+
+ /* Toolbar and slider should have the same elevation */
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ disableToolbarElevation();
+ }
+
+ // Instantiate a ViewPager and a PagerAdapter.
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager(), this);
+
+
+ Bundle fragmentArguments = new Bundle();
+ fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID);
+ mPagerAdapter.setFragmentArgs(fragmentArguments);
+
+ if (mProfile.mUserEditable) {
+ mPagerAdapter.addTab(R.string.basic, Settings_Basic.class);
+ mPagerAdapter.addTab(R.string.server_list, Settings_Connections.class);
+ mPagerAdapter.addTab(R.string.ipdns, Settings_IP.class);
+ mPagerAdapter.addTab(R.string.routing, Settings_Routing.class);
+ mPagerAdapter.addTab(R.string.settings_auth, Settings_Authentication.class);
+
+ mPagerAdapter.addTab(R.string.advanced, Settings_Obscure.class);
+ } else {
+ mPagerAdapter.addTab(R.string.basic, Settings_UserEditable.class);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ mPagerAdapter.addTab(R.string.vpn_allowed_apps, Settings_Allowed_Apps.class);
+
+ mPagerAdapter.addTab(R.string.generated_config, ShowConfigFragment.class);
+
+
+ mPager.setAdapter(mPagerAdapter);
+
+ TabBarView tabs = (TabBarView) findViewById(R.id.sliding_tabs);
+ tabs.setViewPager(mPager);
+
+ }
+
+
+/*
+ @Override
+ public void onBuildHeaders(List<Header> target) {
+ loadHeadersFromResource(R.xml.vpn_headers, target);
+ Header headerToRemove=null;
+ for (Header header : target) {
+ if(header.fragmentArguments==null)
+ header.fragmentArguments = new Bundle();
+ header.fragmentArguments.putString(getPackageName() + ".profileUUID",mProfileUUID);
+ if (header.id == R.id.allowed_apps_header && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
+ headerToRemove = header;
+ }
+ if (headerToRemove != null)
+ target.remove(headerToRemove);
+ }*/
+
+ @Override
+ public void onBackPressed() {
+ setResult(RESULT_OK, getIntent());
+ super.onBackPressed();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.remove_vpn)
+ askProfileRemoval();
+ if (item.getItemId() == R.id.duplicate_vpn) {
+ Intent data = new Intent();
+ data.putExtra(VpnProfile.EXTRA_PROFILEUUID, mProfileUUID);
+ setResult(VPNProfileList.RESULT_VPN_DUPLICATE, data);
+ finish();
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+
+ getMenuInflater().inflate(R.menu.vpnpreferences_menu, menu);
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ private void askProfileRemoval() {
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+ dialog.setTitle("Confirm deletion");
+ dialog.setMessage(getString(R.string.remove_vpn_query, mProfile.mName));
+
+ dialog.setPositiveButton(android.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ removeProfile(mProfile);
+ }
+
+ });
+ dialog.setNegativeButton(android.R.string.no,null);
+ dialog.create().show();
+ }
+
+ protected void removeProfile(VpnProfile profile) {
+ ProfileManager.getInstance(this).removeProfile(this,profile);
+ setResult(VPNProfileList.RESULT_VPN_DELETED);
+ finish();
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private void disableToolbarElevation() {
+ ActionBar toolbar = getActionBar();
+ toolbar.setElevation(0);
+ }
+
+}