From 32b080261845c7508581f9c452d48ffd2401c450 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Fri, 2 Aug 2019 12:50:57 +0200 Subject: Add skeleton build variant --- .../de/blinkt/openvpn/activities/BaseActivity.java | 43 ++ .../blinkt/openvpn/activities/ConfigConverter.java | 847 +++++++++++++++++++++ .../blinkt/openvpn/activities/CreateShortcuts.java | 159 ++++ .../de/blinkt/openvpn/activities/FileSelect.java | 257 +++++++ .../de/blinkt/openvpn/activities/LogWindow.java | 37 + .../de/blinkt/openvpn/activities/MainActivity.java | 131 ++++ .../de/blinkt/openvpn/activities/OpenSSLSpeed.java | 193 +++++ .../blinkt/openvpn/activities/VPNPreferences.java | 244 ++++++ 8 files changed, 1911 insertions(+) create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/CreateShortcuts.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/FileSelect.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/LogWindow.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/OpenSSLSpeed.java create mode 100644 main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.java (limited to 'main/src/ui/java/de/blinkt/openvpn/activities') 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 mPathsegments; + + private String mAliasName = null; + + + private Map fileSelectMap = new HashMap<>(); + private String mEmbeddedPwFile; + private Vector mLogEntries = new Vector<>(); + private Uri mSourceUri; + private EditText mProfilename; + private AsyncTask 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 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 item: fileSelectMap.entrySet()) { + if (item.getValue()==null) + addFileSelectDialog(item.getKey()); + } + } + + private void addFileSelectDialog(Utils.FileType type) { + + Pair 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 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 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() { + 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 vpnList = mPM.getProfiles(); + + Vector vpnNames=new Vector(); + for (VpnProfile vpnProfile : vpnList) { + vpnNames.add(vpnProfile.mName); + } + + + + ArrayAdapter adapter = new ArrayAdapter(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: + * + *
    + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_INTENT} The shortcut intent.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_NAME} The text that will be displayed with + * the shortcut.
  • + *
  • {@link android.content.Intent#EXTRA_SHORTCUT_ICON} The shortcut's icon, if provided as a + * bitmap, or {@link android.content.Intent#EXTRA_SHORTCUT_ICON_RESOURCE} if provided as + * a drawable resource.
  • + *
+ * + * 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(this, mFSFragment)); + bar.addTab(fileExplorerTab); + + if(!mNoInline) { + mInlineFragment = new InlineFileTab(); + inlineFileTab.setTabListener(new MyTabsListener(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 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 { + + 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 { + + + private boolean mCancel = false; + + @Override + protected SpeedResult[] doInBackground(String... strings) { + Vector 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
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); + } + +} -- cgit v1.2.3