diff options
author | Martynas Mickevičius <mmartynas@gmail.com> | 2015-03-08 16:10:09 +0200 |
---|---|---|
committer | Martynas Mickevičius <mmartynas@gmail.com> | 2015-03-08 16:10:09 +0200 |
commit | 23d9368d169b2ce355b68711b0de3e77f742bedb (patch) | |
tree | 88dd38fd77e11ffb276a7e08f7e1b81479e2060c /src/main | |
parent | f08ea668c3059995ea390ab566bd25f3a68b0ef1 (diff) | |
download | andiodine-23d9368d169b2ce355b68711b0de3e77f742bedb.tar.gz andiodine-23d9368d169b2ce355b68711b0de3e77f742bedb.zip |
Gradle build
Diffstat (limited to 'src/main')
39 files changed, 2707 insertions, 0 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0bea991 --- /dev/null +++ b/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.xapek.andiodine" + android:versionCode="3" + android:versionName="1.3"> + + <uses-sdk + android:minSdkVersion="14" + android:targetSdkVersion="16"/> + + <uses-permission android:name="android.permission.INTERNET"/> + + <application + android:allowBackup="false" + android:icon="@drawable/logo" + android:label="@string/app_name" + android:logo="@drawable/logo"> + <activity + android:name="org.xapek.andiodine.IodineMain" + android:label="@string/app_name" + android:screenOrientation="portrait"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + <activity + android:name="org.xapek.andiodine.IodinePref" + android:label="@string/pref_title" + android:launchMode="singleTop" + android:logo="@drawable/logo"> + </activity> + + <service + android:name=".IodineVpnService" + android:permission="android.permission.BIND_VPN_SERVICE"> + <intent-filter> + <action android:name="android.net.VpnService"/> + </intent-filter> + </service> + </application> +</manifest> diff --git a/src/main/java/org/xapek/andiodine/FragmentList.java b/src/main/java/org/xapek/andiodine/FragmentList.java new file mode 100644 index 0000000..10a0875 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/FragmentList.java @@ -0,0 +1,205 @@ +package org.xapek.andiodine; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.DataSetObserver; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import org.xapek.andiodine.config.ConfigDatabase; +import org.xapek.andiodine.config.IodineConfiguration; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class FragmentList extends Fragment { + public static final String TAG = "FRAGMENT_LIST"; + + private ListView mListView; + private ConfigDatabase mConfigDatabase; + private IodineConfiguration mSelectedConfiguration; + private IodineConfigurationAdapter mAdapter; + + private static final int INTENT_REQUEST_CODE_PREPARE = 0; + + private class IodineConfigurationAdapter extends BaseAdapter { + private List<IodineConfiguration> configurations; + private final Set<DataSetObserver> observers = new HashSet<DataSetObserver>(); + + public IodineConfigurationAdapter() { + reload(); + } + + @Override + public int getCount() { + return configurations.size(); + } + + @Override + public IodineConfiguration getItem(int position) { + return configurations.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final IodineConfiguration item = getItem(position); + View view = View.inflate(parent.getContext(), R.layout.configitem, null); + ((TextView) view.findViewById(R.id.configitem_text_name)).setText(item.getName()); + ((TextView) view.findViewById(R.id.configitem_text_topdomain)).setText(item.getTopDomain()); + + view.findViewById(R.id.configitem_btn_manage) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + FragmentList.this.vpnPreferences(item); + } + }); + + view.findViewById(R.id.configitem_layout_name) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + FragmentList.this.vpnServiceConnect(item); + } + }); + + return view; + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + } + + private void reload() { + this.configurations = mConfigDatabase.selectAll(); + triggerOnChanged(); + } + + public void triggerOnChanged() { + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + } + + private final BroadcastReceiver broadcastReceiverConfigurationChanged = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (IodinePref.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + // CONFIGURATION_CHANGED + mAdapter.reload(); + } + } + }; + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mConfigDatabase = new ConfigDatabase(getActivity()); + mListView = (ListView) getActivity().findViewById(R.id.list_view); + mAdapter = new IodineConfigurationAdapter(); + mListView.setAdapter(mAdapter); + + IntentFilter intentFilterConfigurationChanged = new IntentFilter(); + intentFilterConfigurationChanged.addAction(IodinePref.ACTION_CONFIGURATION_CHANGED); + getActivity().registerReceiver(broadcastReceiverConfigurationChanged, intentFilterConfigurationChanged); + + setHasOptionsMenu(true); //activate onCreateOptionsMenu + } + + @Override + public void onDestroy() { + getActivity().unregisterReceiver(broadcastReceiverConfigurationChanged); + mConfigDatabase.close(); + super.onDestroy(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_list, null); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.fragment_list, menu); + } + + private void vpnPreferences(IodineConfiguration item) { + Intent intent = new Intent(getActivity(), IodinePref.class); + intent.putExtra(IodinePref.EXTRA_CONFIGURATION_ID, item.getId()); + startActivity(intent); + } + + private void vpnServiceConnect(IodineConfiguration configuration) { + Intent intent = IodineVpnService.prepare(getActivity()); + mSelectedConfiguration = configuration; + if (intent != null) { + // Ask for permission + intent.putExtra(IodineVpnService.EXTRA_CONFIGURATION_ID, configuration.getId()); + startActivityForResult(intent, INTENT_REQUEST_CODE_PREPARE); + } else { + // Permission already granted + new AlertDialog.Builder(getActivity()) // + .setTitle(R.string.warning) // + .setCancelable(true) // + .setMessage(getString(R.string.main_create_tunnel, configuration.getName())) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + vpnServiceConnect2(mSelectedConfiguration); + } + }) // + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) // + .create() // + .show(); + + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == INTENT_REQUEST_CODE_PREPARE && resultCode == Activity.RESULT_OK) { + vpnServiceConnect2(mSelectedConfiguration); + } + } + + private void vpnServiceConnect2(IodineConfiguration configuration) { + Log.d(TAG, "Call VPN Service for configuration: " + configuration.getId()); + Intent intent = new Intent(IodineVpnService.ACTION_CONTROL_CONNECT); + intent.putExtra(IodineVpnService.EXTRA_CONFIGURATION_ID, configuration.getId()); + getActivity().sendBroadcast(intent); + } +} diff --git a/src/main/java/org/xapek/andiodine/FragmentStatus.java b/src/main/java/org/xapek/andiodine/FragmentStatus.java new file mode 100644 index 0000000..c1f709a --- /dev/null +++ b/src/main/java/org/xapek/andiodine/FragmentStatus.java @@ -0,0 +1,120 @@ +package org.xapek.andiodine; + +import android.app.AlertDialog; +import android.app.Fragment; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ScrollView; +import android.widget.TextView; + +public class FragmentStatus extends Fragment { + public static final String TAG = "FRAGMENT_STATUS"; + + private TextView mStatus; + private TextView mLogmessages; + private ScrollView mScrollview; + private Button mClose; + + private final BroadcastReceiver broadcastReceiverStatusUpdates = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Got intent: " + intent); + if (IodineVpnService.ACTION_STATUS_ERROR.equals(intent.getAction())) { + final TextView message = new TextView(context); + final String stringMessage = intent.getStringExtra(IodineVpnService.EXTRA_ERROR_MESSAGE); + final SpannableString s = new SpannableString(stringMessage); + Linkify.addLinks(s, Linkify.WEB_URLS); + message.setText(s); + message.setMovementMethod(LinkMovementMethod.getInstance()); + new AlertDialog.Builder(FragmentStatus.this.getActivity())// + .setIcon(R.drawable.error) // + .setTitle("Error") // + .setView(message) + .create() // + .show(); + } else if (IodineVpnService.ACTION_STATUS_CONNECT.equals(intent.getAction())) { + mStatus.setText("Connect"); + } else if (IodineVpnService.ACTION_STATUS_CONNECTED.equals(intent.getAction())) { + mStatus.setText("Connected: " + IodineClient.getIp() + '/' + + IodineClient.getNetbits() + " MTU: " + + IodineClient.getMtu() + '\n'); + } else if (IodineVpnService.ACTION_STATUS_DISCONNECT.equals(intent.getAction())) { + mStatus.setText("Disconnect"); + } + } + }; + + private final BroadcastReceiver broadcastReceiverLogMessages = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (IodineClient.ACTION_LOG_MESSAGE.equals(intent.getAction())) { + final String newLogEntry = intent.getStringExtra(IodineClient.EXTRA_MESSAGE); + if (!".".equals(newLogEntry)) // Suppress newline for progress indicator'.' + mLogmessages.append("\n"); + mLogmessages.append(newLogEntry); + mScrollview.fullScroll(View.FOCUS_DOWN); + } + } + }; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mStatus = (TextView) getActivity().findViewById(R.id.status_message); + mLogmessages = (TextView) getActivity().findViewById(R.id.status_logmessages); + mScrollview = (ScrollView) getActivity().findViewById(R.id.status_scrollview); + mClose = (Button) getActivity().findViewById(R.id.status_cancel); + mClose.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + requestDisconnect(); + } + }); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + IntentFilter intentFilterStatusUpdates = new IntentFilter(); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_CONNECT); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_CONNECTED); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_DISCONNECT); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_ERROR); + getActivity().registerReceiver(broadcastReceiverStatusUpdates, intentFilterStatusUpdates); + + Intent intent = new Intent(IodineVpnService.ACTION_CONTROL_UPDATE); + getActivity().sendBroadcast(intent); + + IntentFilter intentFilterLogMessages = new IntentFilter(); + intentFilterLogMessages.addAction(IodineClient.ACTION_LOG_MESSAGE); + getActivity().registerReceiver(broadcastReceiverLogMessages, intentFilterLogMessages); + } + + @Override + public void onDestroy() { + getActivity().unregisterReceiver(broadcastReceiverStatusUpdates); + getActivity().unregisterReceiver(broadcastReceiverLogMessages); + super.onDestroy(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_status, null); + } + + private void requestDisconnect() { + Intent intent = new Intent(IodineVpnService.ACTION_CONTROL_DISCONNECT); + getActivity().sendBroadcast(intent); + } +} diff --git a/src/main/java/org/xapek/andiodine/IodineClient.java b/src/main/java/org/xapek/andiodine/IodineClient.java new file mode 100644 index 0000000..37178b5 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/IodineClient.java @@ -0,0 +1,53 @@ +package org.xapek.andiodine; + +import android.content.Intent; +import android.util.Log; + +public class IodineClient { + public static final String TAG = "NATIVE"; + + public static native int getDnsFd(); + + public static native int connect(String nameserv_addr, String topdomain, boolean raw_mode, boolean lazy_mode, + String password); + + public static native String getIp(); + + public static native String getRemoteIp(); + + public static native int getNetbits(); + + public static native int getMtu(); + + public static native int tunnel(int fd); + + public static native void tunnelInterrupt(); + + public static native String getPropertyNetDns1(); + + /** + * Intent to distribute logmessages from native code + * LOG_MESSAGE(EXTRA_MESSAGE) + */ + public static final String ACTION_LOG_MESSAGE = "org.xapek.andiodine.IodineClient.ACTION_LOG_MESSAGE"; + + public static final String EXTRA_MESSAGE = "message"; + + @SuppressWarnings("UnusedDeclaration") + public static void log_callback(String message) { + Intent intent = new Intent(ACTION_LOG_MESSAGE); + + intent.putExtra(EXTRA_MESSAGE, message); + if (IodineVpnService.instance != null) { + IodineVpnService.instance.sendBroadcast(intent); + } else { + Log.d(TAG, "No VPNService running, cannot broadcast native message"); + } + + } + + static { + System.loadLibrary("iodine-client"); + Log.d(TAG, "Native Library iodine-client loaded"); + } +}
\ No newline at end of file diff --git a/src/main/java/org/xapek/andiodine/IodineMain.java b/src/main/java/org/xapek/andiodine/IodineMain.java new file mode 100644 index 0000000..f631c1d --- /dev/null +++ b/src/main/java/org/xapek/andiodine/IodineMain.java @@ -0,0 +1,112 @@ +package org.xapek.andiodine; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.FragmentTransaction; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import org.xapek.andiodine.config.ConfigDatabase; + +import java.util.Scanner; + +public class IodineMain extends Activity { + private static final String TAG = "MAIN"; + private ConfigDatabase mConfigDatabase; + + private final FragmentList fragmentList = new FragmentList(); + private final FragmentStatus fragmentStatus = new FragmentStatus(); + + private final BroadcastReceiver broadcastReceiverStatusUpdates = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Got intent: " + intent); + if (IodineVpnService.ACTION_STATUS_ERROR.equals(intent.getAction())) { + // Switch to List of Configurations Fragment + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(R.id.main_fragment_status, fragmentList); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.commit(); + } else if (IodineVpnService.ACTION_STATUS_IDLE.equals(intent.getAction())) { + // Switch to List of Configurations Fragment + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(R.id.main_fragment_status, fragmentList); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.commit(); + } else if (IodineVpnService.ACTION_STATUS_CONNECT.equals(intent.getAction()) + || IodineVpnService.ACTION_STATUS_CONNECTED.equals(intent.getAction())) { + // Switch to Status Fragment + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(R.id.main_fragment_status, fragmentStatus); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + ft.commit(); + } + } + }; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + mConfigDatabase = new ConfigDatabase(this); + + startService(new Intent(this, IodineVpnService.class)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter intentFilterStatusUpdates = new IntentFilter(); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_CONNECT); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_CONNECTED); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_ERROR); + intentFilterStatusUpdates.addAction(IodineVpnService.ACTION_STATUS_IDLE); + registerReceiver(broadcastReceiverStatusUpdates, intentFilterStatusUpdates); + + Log.d(TAG, "Request CONTROL_UPDATE"); + sendBroadcast(new Intent(IodineVpnService.ACTION_CONTROL_UPDATE)); + } + + @Override + protected void onPause() { + unregisterReceiver(broadcastReceiverStatusUpdates); + super.onPause(); + } + + @Override + protected void onDestroy() { + mConfigDatabase.close(); + super.onDestroy(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_main_about) { + Scanner scanner = new Scanner(getResources().openRawResource(R.raw.license)); + scanner.useDelimiter("\\A"); + new AlertDialog.Builder(IodineMain.this)// + .setMessage(scanner.next()) // + .setCancelable(true)// + .create() // + .show(); + scanner.close(); + } else if (item.getItemId() == R.id.menu_main_add) { + startActivity(new Intent(this, IodinePref.class)); + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/main/java/org/xapek/andiodine/IodinePref.java b/src/main/java/org/xapek/andiodine/IodinePref.java new file mode 100644 index 0000000..cc21b59 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/IodinePref.java @@ -0,0 +1,112 @@ +package org.xapek.andiodine; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import org.xapek.andiodine.config.ConfigDatabase; +import org.xapek.andiodine.config.IodineConfiguration; +import org.xapek.andiodine.config.IodineConfiguration.NameserverMode; +import org.xapek.andiodine.config.IodineConfiguration.RequestType; + +public class IodinePref extends org.xapek.andiodine.preferences.PreferenceActivity { + public static final String EXTRA_CONFIGURATION_ID = "uuid"; + public static final String ACTION_CONFIGURATION_CHANGED = "org.xapek.andiodine.preferences.PreferenceActivity.CONFIGURATION_CHANGED"; + + private static final String TAG = "PREF"; + + private ConfigDatabase mConfigDatabase; + + private IodineConfiguration mIodineConfiguration; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getActionBar().setDisplayHomeAsUpEnabled(true); + + mConfigDatabase = new ConfigDatabase(this); + Long configurationId = getIntent().getLongExtra(EXTRA_CONFIGURATION_ID, -1); + if (configurationId == null || configurationId == -1) { + // Configuration ID is empty; create new configuration + mIodineConfiguration = new IodineConfiguration(); + } else { + mIodineConfiguration = mConfigDatabase.selectById(configurationId); + if (mIodineConfiguration == null) { + Log.e(TAG, "No configuration with uuid: " + configurationId + " found"); + finish(); + } + } + setContentValues(mIodineConfiguration.getContentValues()); + + // Name + addPreference(ConfigDatabase.COLUMN_CONF_NAME, "Name", R.string.pref_help_name, "New Connection"); + // Topdomain + addPreference(ConfigDatabase.COLUMN_CONF_TOP_DOMAIN, "Tunnel Topdomain", R.string.pref_help_topdomain, + "tun.example.com"); + // Password + addPreference(ConfigDatabase.COLUMN_CONF_PASSWORD, "Password", R.string.pref_help_password, ""); + // Tunnel Nameserver + addPreference(ConfigDatabase.COLUMN_CONF_TUNNEL_NAMESERVER, "Tunnel Nameserver (or empty)", + R.string.pref_help_tunnel_nameserver, ""); + // Nameserver Mode + String[] nameserverModes = new String[NameserverMode.values().length]; + for (int i = 0; i < NameserverMode.values().length; i++) { + nameserverModes[i] = NameserverMode.values()[i].name(); + } + addPreference(ConfigDatabase.COLUMN_CONF_NAMESERVER_MODE, "Nameserver Mode", + R.string.pref_help_nameserver_mode, nameserverModes, NameserverMode.LEAVE_DEFAULT.name()); + // Nameserver + addPreference(ConfigDatabase.COLUMN_CONF_NAMESERVER, "Nameserver", R.string.pref_help_nameserver, ""); + // Request Type + String[] requestTypes = new String[RequestType.values().length]; + for (int i = 0; i < RequestType.values().length; i++) { + requestTypes[i] = RequestType.values()[i].name(); + } + addPreference(ConfigDatabase.COLUMN_CONF_REQUEST_TYPE, "Request Type", R.string.pref_help_request_type, + requestTypes, RequestType.AUTODETECT.name()); + // Lazy Mode + addPreference(ConfigDatabase.COLUMN_CONF_LAZY_MODE, "Lazy mode", R.string.pref_help_lazy, true); + // Raw Mode + addPreference(ConfigDatabase.COLUMN_CONF_RAW_MODE, "Raw Mode", R.string.pref_help_raw, false); + // Default Route + addPreference(ConfigDatabase.COLUMN_CONF_DEFAULT_ROUTE, "Default Route", R.string.pref_help_default_route, true); + } + + @Override + protected void onDestroy() { + mConfigDatabase.close(); + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.pref, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected void onStop() { + mConfigDatabase.insertOrUpdate(mIodineConfiguration.getContentValues()); + Intent intent = new Intent(ACTION_CONFIGURATION_CHANGED); + intent.putExtra(EXTRA_CONFIGURATION_ID, mIodineConfiguration.getId()); + sendBroadcast(intent); + super.onStop(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.menu_pref_delete) { + // Delete current connection + if (mIodineConfiguration.getId() != null) { + mConfigDatabase.delete(mIodineConfiguration.getContentValues()); + } + finish(); + } else if (item.getItemId() == android.R.id.home) { + finish(); + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/main/java/org/xapek/andiodine/IodineVpnService.java b/src/main/java/org/xapek/andiodine/IodineVpnService.java new file mode 100644 index 0000000..2adad1c --- /dev/null +++ b/src/main/java/org/xapek/andiodine/IodineVpnService.java @@ -0,0 +1,350 @@ +package org.xapek.andiodine; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.VpnService; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import org.xapek.andiodine.config.ConfigDatabase; +import org.xapek.andiodine.config.IodineConfiguration; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class IodineVpnService extends VpnService implements Runnable { + private class IodineVpnException extends Exception { + private static final long serialVersionUID = 32487871521160156L; + + public IodineVpnException(String message, Throwable e) { + super(message, e); + } + + public IodineVpnException(String message) { + super(message); + } + } + + private static final String TAG = "VPN_SERVICE"; + + public static IodineVpnService instance = null; + + /** + * long + */ + public static final String EXTRA_CONFIGURATION_ID = "configuration_id"; + /** + * String + */ + public static final String EXTRA_ERROR_MESSAGE = "message"; + + /** + * Intent to connect to VPN Connection + * CONTROL_CONNECT(EXTRA_CONFIGURATION_ID) + */ + + public static final String ACTION_CONTROL_CONNECT = "org.xapek.andiodine.IodineVpnService.CONTROL_CONNECT"; + /** + * Intent to close the vpn connection + */ + public static final String ACTION_CONTROL_DISCONNECT = "org.xapek.andiodine.IodineVpnService.CONTROL_DISCONNECT"; + + /** + * Intent to request a new status update + */ + public static final String ACTION_CONTROL_UPDATE = "org.xapek.andiodine.IodineVpnService.CONTROL_UPDATE"; + + /** + * Broadcast Action: The tunnel service is idle. This Action contains no + * extras. + */ + public static final String ACTION_STATUS_IDLE = "org.xapek.andiodine.IodineVpnService.STATUS_IDLE"; + + /** + * Broadcast Action: The user sent CONTROL_CONNECT and the vpn service is + * trying to connect. + * + * @see #EXTRA_CONFIGURATION_ID + */ + public static final String ACTION_STATUS_CONNECT = "org.xapek.andiodine.IodineVpnService.STATUS_CONNECT"; + /** + * Broadcast Action: The tunnel is connected + * + * @see #EXTRA_CONFIGURATION_ID + */ + public static final String ACTION_STATUS_CONNECTED = "org.xapek.andiodine.IodineVpnService.STATUS_CONNECTED"; + /** + * Broadcast Action: The tunnel was disconnected + * + * @see #EXTRA_CONFIGURATION_ID + */ + public static final String ACTION_STATUS_DISCONNECT = "org.xapek.andiodine.IodineVpnService.STATUS_DISCONNECT"; + /** + * Broadcast Action: An error occured + * + * @see #EXTRA_CONFIGURATION_ID + * @see #EXTRA_ERROR_MESSAGE + */ + public static final String ACTION_STATUS_ERROR = "org.xapek.andiodine.IodineVpnService.STATUS_ERROR"; + + private Thread mThread; + private ConfigDatabase configDatabase; + private IodineConfiguration mConfiguration; + private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_CONTROL_DISCONNECT.equals(intent.getAction())) { + shutdown(); + } else if (ACTION_CONTROL_UPDATE.equals(intent.getAction())) { + sendStatus(); + } else if (ACTION_CONTROL_CONNECT.equals(intent.getAction())) { + if (mThread != null) { + setStatus(ACTION_STATUS_ERROR, -1L, getString(R.string.vpnservice_error_already_running)); + return; + } + + long configurationId = intent.getLongExtra(EXTRA_CONFIGURATION_ID, -1); + if (configurationId == -1L) { + setStatus(ACTION_STATUS_ERROR, -1L, getString(R.string.vpnservice_error_configuration_incomplete)); + return; + } + + mConfiguration = configDatabase.selectById(configurationId); + if (mConfiguration == null) { + setStatus(ACTION_STATUS_ERROR, mConfiguration.getId(), + getString(R.string.vpnservice_error_configuration_incomplete)); + return; + } + + mThread = new Thread(IodineVpnService.this, IodineVpnService.class.getName()); + mThread.start(); + } + } + }; + + private String currentActionStatus = ACTION_STATUS_IDLE; + + private Long currentConfigurationId = null; + + private String currentMessage = null; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + + configDatabase = new ConfigDatabase(this); + + IntentFilter filterInterruptAction = new IntentFilter(); + filterInterruptAction.addAction(ACTION_CONTROL_CONNECT); + filterInterruptAction.addAction(ACTION_CONTROL_DISCONNECT); + filterInterruptAction.addAction(ACTION_CONTROL_UPDATE); + registerReceiver(broadcastReceiver, filterInterruptAction); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + sendStatus(); + return START_STICKY; + } + + private void shutdown() { + if (mConfiguration != null) + setStatus(ACTION_STATUS_DISCONNECT, mConfiguration.getId(), null); + else + setStatus(ACTION_STATUS_IDLE, null, null); + + if (mThread != null) { + mThread.interrupt(); + IodineClient.tunnelInterrupt(); + } + } + + @Override + public void onRevoke() { + shutdown(); + } + + @Override + public void onDestroy() { + if (mThread != null) { + mThread.interrupt(); + IodineClient.tunnelInterrupt(); + mThread = null; + } + instance = null; + configDatabase.close(); + unregisterReceiver(broadcastReceiver); + } + + private void setStatus(String ACTION_STATUS, Long configurationId, String message) { + currentActionStatus = ACTION_STATUS; + currentConfigurationId = configurationId; + currentMessage = message; + sendStatus(); + } + + private void sendStatus() { + Log.d(TAG, "Send status: " + currentActionStatus); + if (currentActionStatus != null) { + Intent intent = new Intent(currentActionStatus); + + if (currentConfigurationId != null) { + intent.putExtra(EXTRA_CONFIGURATION_ID, currentConfigurationId); + } + if (currentMessage != null) { + intent.putExtra(EXTRA_ERROR_MESSAGE, currentMessage); + } + Log.d(TAG, "Send: " + intent); + sendBroadcast(intent); + } + } + + @Override + public void run() { + try { + Log.d(TAG, "VPN Thread enter"); + setStatus(ACTION_STATUS_CONNECT, mConfiguration.getId(), null); + + String tunnelNamesver = IodineClient.getPropertyNetDns1(); + if (!"".equals(mConfiguration.getTunnelNameserver())) { + tunnelNamesver = mConfiguration.getTunnelNameserver(); + } + String password = ""; + if (!"".equals(mConfiguration.getPassword())) { + password = mConfiguration.getPassword(); + } + + int ret = IodineClient.connect(tunnelNamesver, mConfiguration.getTopDomain(), mConfiguration.getRawMode(), + mConfiguration.getLazyMode(), password); + + String errorMessage = ""; + switch (ret) { + case 0: + Log.d(TAG, "Handshake successful"); + setStatus(ACTION_STATUS_CONNECTED, currentConfigurationId, null); + runTunnel(); // this blocks until connection is closed + setStatus(ACTION_STATUS_IDLE, null, null); + break; + case 1: + if (errorMessage.equals("")) + errorMessage = getString(R.string.vpnservice_error_cant_open_dnssocket); + // Fall through + case 2: + if (errorMessage.equals("")) + errorMessage = getString(R.string.vpnservice_error_handshake_failed); + // fall through + default: + if (errorMessage.equals("")) + errorMessage = String.format(getString(R.string.vpnservice_error_unknown_error_code), ret); + + setStatus(ACTION_STATUS_ERROR, mConfiguration.getId(), errorMessage); + break; + } + } catch (IllegalStateException e) { + String errorMessage = "IllegalStateException"; + if (e.getMessage().contains("Cannot create interface")) { + errorMessage = "Failed to create tunnel network device"; + } else { + e.printStackTrace(); + } + setStatus(ACTION_STATUS_ERROR, mConfiguration.getId(), errorMessage); + } catch (IodineVpnException e) { + e.printStackTrace(); + + setStatus(ACTION_STATUS_ERROR, mConfiguration.getId(), + String.format(getString(R.string.vpnservice_error_unknown_error_string), e.getMessage())); + } finally { + mThread = null; + mConfiguration = null; + Log.d(TAG, "VPN Thread exit"); + } + } + + private void runTunnel() throws IodineVpnException { + Builder b = new Builder(); + b.setSession("Iodine VPN Service"); + + String ip = IodineClient.getIp(); + int netbits = IodineClient.getNetbits(); + int mtu = IodineClient.getMtu(); + Log.d(TAG, "Build tunnel for configuration: ip=" + ip + " netbits=" + netbits + " mtu=" + mtu); + + String[] ipBytesString = ip.split("\\."); + if (ipBytesString.length != 4) { + throw new IodineVpnException("Server sent invalid IP"); + } + byte[] ipBytes = new byte[4]; + for (int i = 0; i < 4; i++) { + try { + int integer = Integer.parseInt(ipBytesString[i]); + ipBytes[i] = (byte) (integer); + } catch (NumberFormatException e) { + throw new IodineVpnException("Server sent invalid IP", e); + } + } + + InetAddress hostAddress; + try { + hostAddress = InetAddress.getByAddress(ipBytes); + } catch (UnknownHostException e) { + throw new IodineVpnException("Server sent invalid IP", e); + } + try { + switch (mConfiguration.getNameserverMode()) { + case LEAVE_DEFAULT: + // do nothing + break; + case SET_SERVER_TUNNEL_IP: + b.addDnsServer(IodineClient.getRemoteIp()); + break; + case SET_CUSTOM: + b.addDnsServer(InetAddress.getByName(mConfiguration.getNameserver())); + break; + default: + throw new IodineVpnException("Invalid Nameserver mode"); + } + } catch (UnknownHostException e) { + throw new IodineVpnException("Invalid Nameserver address", e); + } + + b.addAddress(hostAddress, netbits); + if (mConfiguration.getDefaultRoute()) { + Log.d(TAG, "Set default route"); + b.addRoute("0.0.0.0", 0); // Default Route + } + b.setMtu(mtu); + + Log.d(TAG, "Build tunnel interface"); + ParcelFileDescriptor parcelFD; + try { + parcelFD = b.establish(); + } catch (Exception e) { + if (e.getMessage().contains("fwmark") || e.getMessage().contains("iptables")) { + throw new IodineVpnException( + "Error while creating interface, please check issue #9 at https://github.com/yvesf/andiodine/issues/9"); + } else { + throw new IodineVpnException("Error while creating interface: " + + e.getMessage()); + } + } + + protect(IodineClient.getDnsFd()); + + int tun_fd = parcelFD.detachFd(); + + setStatus(ACTION_STATUS_CONNECTED, mConfiguration.getId(), null); + + Log.d(TAG, "Tunnel active"); + IodineClient.tunnel(tun_fd); + try { + ParcelFileDescriptor.adoptFd(tun_fd).close(); + } catch (IOException e) { + throw new IodineVpnException( + "Failed to close fd after tunnel exited"); + } + } +} diff --git a/src/main/java/org/xapek/andiodine/config/ConfigDatabase.java b/src/main/java/org/xapek/andiodine/config/ConfigDatabase.java new file mode 100644 index 0000000..c4245ba --- /dev/null +++ b/src/main/java/org/xapek/andiodine/config/ConfigDatabase.java @@ -0,0 +1,124 @@ +package org.xapek.andiodine.config; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +public class ConfigDatabase extends SQLiteOpenHelper { + public static final String TAG = "ConfigDatabase"; + + private static final String DATABASE_NAME = "andiodine.db"; + private static final int DATABASE_VERSION = 1; + static public final String TABLE_NAME_CONF = "configuration"; + static public final String COLUMN_CONF_ID = "id"; + static public final String COLUMN_CONF_NAME = "name"; + static public final String COLUMN_CONF_LAST_USED = "last_used"; + static public final String COLUMN_CONF_TUNNEL_NAMESERVER = "tunnel_nameserver"; + static public final String COLUMN_CONF_TOP_DOMAIN = "top_domain"; + static public final String COLUMN_CONF_PASSWORD = "password"; + static public final String COLUMN_CONF_NAMESERVER_MODE = "nameserver_mode"; + static public final String COLUMN_CONF_NAMESERVER = "nameserver"; + static public final String COLUMN_CONF_RAW_MODE = "raw_mode"; + static public final String COLUMN_CONF_LAZY_MODE = "lazy_mode"; + static public final String COLUMN_CONF_DEFAULT_ROUTE = "default_route"; + static public final String COLUMN_CONF_REQUEST_TYPE = "request_type"; + + private static final String createStmt = "CREATE TABLE " + TABLE_NAME_CONF + " (" + // + COLUMN_CONF_ID + " INTEGER PRIMARY KEY," + // + COLUMN_CONF_NAME + " TEXT," + // + COLUMN_CONF_LAST_USED + " INTEGER," + // + COLUMN_CONF_TUNNEL_NAMESERVER + " TEXT," + // + COLUMN_CONF_TOP_DOMAIN + " TEXT," + // + COLUMN_CONF_PASSWORD + " TEXT," + // + COLUMN_CONF_NAMESERVER_MODE + " TEXT," + // + COLUMN_CONF_NAMESERVER + " TEXT," + // + COLUMN_CONF_RAW_MODE + " INTEGER," + // Boolean stored as 1=true / 0=false + COLUMN_CONF_LAZY_MODE + " INTEGER," + // + COLUMN_CONF_DEFAULT_ROUTE + " INTEGER," + // + COLUMN_CONF_REQUEST_TYPE + " TEXT" + // + ");"; + + public ConfigDatabase(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(createStmt); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + + public void insert(ContentValues config) throws SQLException { + if (config.getAsLong(COLUMN_CONF_ID) != null) + throw new SQLException("id must be null for update"); + SQLiteDatabase writableDatabase = getWritableDatabase(); + long id = writableDatabase.insertOrThrow(TABLE_NAME_CONF, null, config); + writableDatabase.close(); + config.put(COLUMN_CONF_ID, id); + Log.d(TAG, "Insert id=" + id); + } + + public int update(ContentValues config) throws SQLException { + if (config.getAsLong(COLUMN_CONF_ID) == null) + throw new SQLException("id must NOT be null for update"); + SQLiteDatabase writableDatabase = getWritableDatabase(); + int rows = writableDatabase.update(TABLE_NAME_CONF, config, COLUMN_CONF_ID + " = ?", + new String[]{config.getAsString(COLUMN_CONF_ID)}); + writableDatabase.close(); + Log.d(TAG, "Update rows=" + rows); + return rows; + } + + public void delete(ContentValues config) throws SQLException { + if (config.getAsLong(COLUMN_CONF_ID) == null) + throw new SQLException("id must NOT be null for delete"); + SQLiteDatabase writableDatabase = getWritableDatabase(); + writableDatabase.delete(TABLE_NAME_CONF, COLUMN_CONF_ID + " = ?", + new String[]{config.getAsString(COLUMN_CONF_ID)}); + writableDatabase.close(); + } + + public IodineConfiguration selectById(Long id) throws SQLException { + ContentValues v = new ContentValues(); + SQLiteDatabase readableDatabase = getReadableDatabase(); + Cursor query = readableDatabase.query(TABLE_NAME_CONF, null, COLUMN_CONF_ID + " = ?", + new String[]{id.toString()}, null, null, null); + query.moveToFirst(); + DatabaseUtils.cursorRowToContentValues(query, v); + IodineConfiguration iodineConfiguration = new IodineConfiguration(v); + Log.d(TAG, "Selected: " + iodineConfiguration); + return iodineConfiguration; + } + + public List<IodineConfiguration> selectAll() throws SQLException { + List<IodineConfiguration> configurations = new ArrayList<IodineConfiguration>(); + SQLiteDatabase readableDatabase = getReadableDatabase(); + Cursor query = readableDatabase.query(TABLE_NAME_CONF, null, null, null, null, null, null); + + while (query.moveToNext()) { + ContentValues v = new ContentValues(); + DatabaseUtils.cursorRowToContentValues(query, v); + configurations.add(new IodineConfiguration(v)); + } + return configurations; + } + + public void insertOrUpdate(ContentValues config) { + try { + update(config); + } catch (SQLException e) { + insert(config); + } + } +} diff --git a/src/main/java/org/xapek/andiodine/config/IodineConfiguration.java b/src/main/java/org/xapek/andiodine/config/IodineConfiguration.java new file mode 100644 index 0000000..21aac6c --- /dev/null +++ b/src/main/java/org/xapek/andiodine/config/IodineConfiguration.java @@ -0,0 +1,132 @@ +package org.xapek.andiodine.config; + +import android.content.ContentValues; + +/** + * Wrapper around ContentValues in Database + */ +public class IodineConfiguration { + public static enum NameserverMode { + LEAVE_DEFAULT, SET_SERVER_TUNNEL_IP, SET_CUSTOM + } + + public static enum RequestType { + AUTODETECT, NULL, TXT, SRV, MX, CNAME, A + } + + private final ContentValues v; + + public IodineConfiguration() { + v = new ContentValues(); + } + + public IodineConfiguration(ContentValues v) { + this.v = v; + } + + @Override + public boolean equals(Object o) { + if (o instanceof IodineConfiguration) { + return getId() != null && getId().equals(((IodineConfiguration) o).getId()); + } else { + return super.equals(o); + } + } + + public Long getId() { + return v.getAsLong(ConfigDatabase.COLUMN_CONF_ID); + } + + public boolean getDefaultRoute() { + return v.getAsInteger(ConfigDatabase.COLUMN_CONF_DEFAULT_ROUTE) == 1; + } + + public void setDefaultRoute(boolean isDefaultRoute) { + v.put(ConfigDatabase.COLUMN_CONF_DEFAULT_ROUTE, isDefaultRoute ? 1 : 0); + } + + public boolean getLazyMode() { + return v.getAsInteger(ConfigDatabase.COLUMN_CONF_LAZY_MODE) == 1; + } + + public String getName() { + return v.getAsString(ConfigDatabase.COLUMN_CONF_NAME); + } + + public String getNameserver() { + return v.getAsString(ConfigDatabase.COLUMN_CONF_NAMESERVER); + } + + public NameserverMode getNameserverMode() { + return NameserverMode.valueOf(v.getAsString(ConfigDatabase.COLUMN_CONF_NAMESERVER_MODE)); + } + + public String getPassword() { + return v.getAsString(ConfigDatabase.COLUMN_CONF_PASSWORD); + } + + public boolean getRawMode() { + return v.getAsInteger(ConfigDatabase.COLUMN_CONF_RAW_MODE) == 1; + } + + public RequestType getRequestType() { + return RequestType.valueOf(v.getAsString(ConfigDatabase.COLUMN_CONF_REQUEST_TYPE)); + } + + public String getTopDomain() { + return v.getAsString(ConfigDatabase.COLUMN_CONF_TOP_DOMAIN); + } + + public String getTunnelNameserver() { + return v.getAsString(ConfigDatabase.COLUMN_CONF_TUNNEL_NAMESERVER); + } + + public void setLazyMode(boolean lazyMode) { + v.put(ConfigDatabase.COLUMN_CONF_LAZY_MODE, lazyMode ? 1 : 0); + } + + public void setName(String name) { + v.put(ConfigDatabase.COLUMN_CONF_NAME, name); + } + + public void setNameserver(String nameserver) { + v.put(ConfigDatabase.COLUMN_CONF_NAMESERVER, nameserver); + } + + public void setNameserverMode(NameserverMode nameserverMode) { + v.put(ConfigDatabase.COLUMN_CONF_NAMESERVER_MODE, nameserverMode.name()); + } + + public void setPassword(String password) { + v.put(ConfigDatabase.COLUMN_CONF_PASSWORD, password); + } + + public void setRawMode(boolean rawMode) { + v.put(ConfigDatabase.COLUMN_CONF_RAW_MODE, rawMode ? 1 : 0); + } + + public void setRequestType(RequestType requestType) { + v.put(ConfigDatabase.COLUMN_CONF_REQUEST_TYPE, requestType.name()); + } + + public void setTopDomain(String topDomain) { + v.put(ConfigDatabase.COLUMN_CONF_TOP_DOMAIN, topDomain); + } + + public void setTunnelNameserver(String tunnelNameserver) { + v.put(ConfigDatabase.COLUMN_CONF_TUNNEL_NAMESERVER, tunnelNameserver); + } + + public void setId(Long id) { + v.put(ConfigDatabase.COLUMN_CONF_ID, id); + } + + public ContentValues getContentValues() { + return v; + } + + @Override + public String toString() { + return "[IodineConfiguration name=" + getName() + "]"; + } +} diff --git a/src/main/java/org/xapek/andiodine/preferences/AbstractPreference.java b/src/main/java/org/xapek/andiodine/preferences/AbstractPreference.java new file mode 100644 index 0000000..278de63 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/preferences/AbstractPreference.java @@ -0,0 +1,49 @@ +package org.xapek.andiodine.preferences; + +import android.content.Context; +import android.util.Log; +import android.view.View; + +abstract class AbstractPreference { + public static final String TAG = "Preference"; + private final String mKey; + private final PreferenceActivity mPreferenceActivity; + private final String mTitle; + private final int mHelpMsgId; + + public AbstractPreference(PreferenceActivity preferenceActivity, String title, int helpResId, String key) { + mPreferenceActivity = preferenceActivity; + mTitle = title; + mHelpMsgId = helpResId; + mKey = key; + } + + protected abstract View getListItemView(Context context); + + public void persist(final String value) { + Log.d(TAG, String.format("persist String %s -> %s", mKey, value)); + mPreferenceActivity.getContentValues().put(mKey, value); + } + + public void persist(final boolean value) { + Log.d(TAG, String.format("persist boolean %s -> %s", mKey, "" + value)); + mPreferenceActivity.getContentValues().put(mKey, value ? 1 : 0); + } + + public boolean getAsBoolean() { + return mPreferenceActivity.getContentValues().getAsInteger(mKey) != null + && mPreferenceActivity.getContentValues().getAsInteger(mKey) == 1; + } + + public String getAsString() { + return mPreferenceActivity.getContentValues().getAsString(mKey); + } + + public String getTitle() { + return mTitle; + } + + public String getMessage() { + return mPreferenceActivity.getResources().getString(mHelpMsgId); + } +} diff --git a/src/main/java/org/xapek/andiodine/preferences/BooleanPreference.java b/src/main/java/org/xapek/andiodine/preferences/BooleanPreference.java new file mode 100644 index 0000000..9e7917d --- /dev/null +++ b/src/main/java/org/xapek/andiodine/preferences/BooleanPreference.java @@ -0,0 +1,33 @@ +package org.xapek.andiodine.preferences; + +import android.content.Context; +import android.util.Log; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; + +import org.xapek.andiodine.R; + +public class BooleanPreference extends AbstractPreference { + + public BooleanPreference(PreferenceActivity preferenceActivity, String title, int helpMsgId, String key) { + super(preferenceActivity, title, helpMsgId, key); + } + + @Override + protected View getListItemView(Context context) { + CheckBox view = new CheckBox(context); + view.setText(context.getString(R.string.enable) + " " + getTitle()); + Log.d(TAG, "Status: " + getTitle() + " = " + getAsBoolean()); + view.setChecked(getAsBoolean()); + view.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + persist(isChecked); + } + }); + return view; + } + +} diff --git a/src/main/java/org/xapek/andiodine/preferences/PreferenceActivity.java b/src/main/java/org/xapek/andiodine/preferences/PreferenceActivity.java new file mode 100644 index 0000000..dd0faa4 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/preferences/PreferenceActivity.java @@ -0,0 +1,104 @@ +package org.xapek.andiodine.preferences; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.ContentValues; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import org.xapek.andiodine.R; + +import java.util.ArrayList; +import java.util.List; + +public abstract class PreferenceActivity extends ListActivity { + private ArrayList<AbstractPreference> mPreferences; + + private ContentValues mContentValues = new ContentValues(); + + private static class DialogPreferenceAdapter extends ArrayAdapter<AbstractPreference> { + + private final LayoutInflater mInflater; + + private static class HelpOnClickListener implements OnClickListener { + private final AbstractPreference p; + + public HelpOnClickListener(AbstractPreference p) { + this.p = p; + } + + @Override + public void onClick(View v) { + new AlertDialog.Builder(v.getContext())// + .setTitle(p.getTitle())// + .setMessage(p.getMessage())// + .create().show(); + } + } + + public DialogPreferenceAdapter(Context context, List<AbstractPreference> preferences) { + super(context, -1, preferences); + mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + AbstractPreference item = getItem(position); + View rowView = mInflater.inflate(R.layout.rowlayout, parent, false); + LinearLayout content = (LinearLayout) rowView.findViewById(R.id.rowlayout_content); + ImageButton helpButton = (ImageButton) rowView.findViewById(R.id.rowlayout_help); + TextView title = (TextView) rowView.findViewById(R.id.rowlayout_title); + + content.addView(item.getListItemView(getContext())); + helpButton.setOnClickListener(new HelpOnClickListener(item)); + title.setText(item.getTitle()); + + return rowView; + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mPreferences = new ArrayList<AbstractPreference>(); + + setListAdapter(new DialogPreferenceAdapter(this, mPreferences)); + } + + public void setContentValues(final ContentValues contentValues) { + mContentValues = contentValues; + } + + public ContentValues getContentValues() { + return mContentValues; + } + + public void addPreference(String key, String title, int helpMsgId, String defaultValue) { + if (mContentValues.get(key) == null) + mContentValues.put(key, defaultValue); + mPreferences.add(new TextPreference(this, title, helpMsgId, key)); + } + + public void addPreference(String key, String title, int helpMsgId, boolean defaultValue) { + BooleanPreference preference = new BooleanPreference(this, title, helpMsgId, key); + if (mContentValues.get(key) == null) + preference.persist(defaultValue); + mPreferences.add(preference); + } + + public void addPreference(String key, String title, int helpMsgId, String[] values, String defaultValue) { + if (mContentValues.get(key) == null) + mContentValues.put(key, defaultValue); + mPreferences.add(new SpinnerPreference(this, key, title, helpMsgId, values)); + } +} diff --git a/src/main/java/org/xapek/andiodine/preferences/SpinnerPreference.java b/src/main/java/org/xapek/andiodine/preferences/SpinnerPreference.java new file mode 100644 index 0000000..3528f05 --- /dev/null +++ b/src/main/java/org/xapek/andiodine/preferences/SpinnerPreference.java @@ -0,0 +1,47 @@ +package org.xapek.andiodine.preferences; + +import android.content.Context; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Spinner; + +public class SpinnerPreference extends AbstractPreference { + private final String[] mValues; + + public SpinnerPreference(PreferenceActivity preferenceActivity, String key, String title, int helpResId, + String[] values) { + super(preferenceActivity, title, helpResId, key); + mValues = values; + } + + @Override + protected View getListItemView(Context context) { + Spinner view = new Spinner(context); + view.setAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, mValues)); + + int i = 0; + for (final String value : mValues) { + if (value != null && value.equals(getAsString())) { + view.setSelection(i); + break; + } + i++; + } + + view.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + String item = mValues[position]; + persist(item); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + return view; + } +} diff --git a/src/main/java/org/xapek/andiodine/preferences/TextPreference.java b/src/main/java/org/xapek/andiodine/preferences/TextPreference.java new file mode 100644 index 0000000..ae28f1e --- /dev/null +++ b/src/main/java/org/xapek/andiodine/preferences/TextPreference.java @@ -0,0 +1,35 @@ +package org.xapek.andiodine.preferences; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; + +public class TextPreference extends AbstractPreference { + public TextPreference(PreferenceActivity preferenceActivity, String title, int helpMsgId, String key) { + super(preferenceActivity, title, helpMsgId, key); + } + + @Override + protected View getListItemView(Context context) { + final EditText view = new EditText(context); + view.setSingleLine(); + view.setText(getAsString()); + view.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) { + persist(s.toString()); + } + }); + return view; + } +} diff --git a/src/main/res/anim/main_status_image_connect.xml b/src/main/res/anim/main_status_image_connect.xml new file mode 100644 index 0000000..dadf0d6 --- /dev/null +++ b/src/main/res/anim/main_status_image_connect.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<scale xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="200" + android:fromXScale="1.0" + android:fromYScale="1.0" + android:interpolator="@android:anim/overshoot_interpolator" + android:pivotX="50%" + android:pivotY="50%" + android:repeatCount="infinite" + android:repeatMode="restart" + android:toXScale="0.8" + android:toYScale="0.8" > + +</scale>
\ No newline at end of file diff --git a/src/main/res/anim/main_status_image_connected.xml b/src/main/res/anim/main_status_image_connected.xml new file mode 100644 index 0000000..e9aba80 --- /dev/null +++ b/src/main/res/anim/main_status_image_connected.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<rotate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="2000" + android:fromDegrees="0" + android:interpolator="@android:anim/linear_interpolator" + android:pivotX="50%" + android:pivotY="50%" + android:repeatCount="infinite" + android:repeatMode="restart" + android:toDegrees="359" > +</rotate>
\ No newline at end of file diff --git a/src/main/res/anim/main_status_image_disconnect.xml b/src/main/res/anim/main_status_image_disconnect.xml new file mode 100644 index 0000000..49677b5 --- /dev/null +++ b/src/main/res/anim/main_status_image_disconnect.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<translate xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="200" + android:fromXDelta="20" + android:repeatCount="infinite" + android:repeatMode="reverse" + android:toXDelta="-20" > + +</translate>
\ No newline at end of file diff --git a/src/main/res/anim/main_status_image_error.xml b/src/main/res/anim/main_status_image_error.xml new file mode 100644 index 0000000..55472d0 --- /dev/null +++ b/src/main/res/anim/main_status_image_error.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator" > + + <set + android:duration="200" + android:interpolator="@android:anim/anticipate_overshoot_interpolator" > + <scale + android:fromXScale="1.0" + android:fromYScale="1.0" + android:pivotX="50%" + android:pivotY="50%" + android:repeatCount="infinite" + android:repeatMode="reverse" + android:startOffset="800" + android:toXScale="2.8" + android:toYScale="0.0" /> + </set> + +</set>
\ No newline at end of file diff --git a/src/main/res/anim/main_status_image_idle.xml b/src/main/res/anim/main_status_image_idle.xml new file mode 100644 index 0000000..68802e7 --- /dev/null +++ b/src/main/res/anim/main_status_image_idle.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" > + +</set>
\ No newline at end of file diff --git a/src/main/res/drawable/action_help.png b/src/main/res/drawable/action_help.png Binary files differnew file mode 100644 index 0000000..1bf05f2 --- /dev/null +++ b/src/main/res/drawable/action_help.png diff --git a/src/main/res/drawable/cancel.png b/src/main/res/drawable/cancel.png Binary files differnew file mode 100644 index 0000000..d743d75 --- /dev/null +++ b/src/main/res/drawable/cancel.png diff --git a/src/main/res/drawable/delete.png b/src/main/res/drawable/delete.png Binary files differnew file mode 100644 index 0000000..a9d4d6e --- /dev/null +++ b/src/main/res/drawable/delete.png diff --git a/src/main/res/drawable/device_access_new_account.png b/src/main/res/drawable/device_access_new_account.png Binary files differnew file mode 100644 index 0000000..6e92072 --- /dev/null +++ b/src/main/res/drawable/device_access_new_account.png diff --git a/src/main/res/drawable/error.png b/src/main/res/drawable/error.png Binary files differnew file mode 100644 index 0000000..2d0283e --- /dev/null +++ b/src/main/res/drawable/error.png diff --git a/src/main/res/drawable/ic_bt_config.png b/src/main/res/drawable/ic_bt_config.png Binary files differnew file mode 100644 index 0000000..6754469 --- /dev/null +++ b/src/main/res/drawable/ic_bt_config.png diff --git a/src/main/res/drawable/logo.png b/src/main/res/drawable/logo.png Binary files differnew file mode 100644 index 0000000..c60295a --- /dev/null +++ b/src/main/res/drawable/logo.png diff --git a/src/main/res/layout/configitem.xml b/src/main/res/layout/configitem.xml new file mode 100644 index 0000000..d8d279d --- /dev/null +++ b/src/main/res/layout/configitem.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:orientation="horizontal" > + + <LinearLayout + android:id="@+id/configitem_layout_name" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_weight="1" + android:clickable="true" + android:orientation="vertical" > + + <TextView + android:id="@+id/configitem_text_name" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="name der verbindung" + tools:ignore="HardcodedText" /> + + <TextView + android:id="@+id/configitem_text_topdomain" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="xxx.topdomain.org" + tools:ignore="HardcodedText" /> + </LinearLayout> + + <ImageButton + android:id="@+id/configitem_btn_manage" + android:layout_width="50dp" + android:layout_height="50dp" + android:background="@drawable/ic_bt_config" + android:contentDescription="@string/configitem_description_manage" /> + +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/fragment_list.xml b/src/main/res/layout/fragment_list.xml new file mode 100644 index 0000000..3b1e678 --- /dev/null +++ b/src/main/res/layout/fragment_list.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + + <ListView + android:id="@+id/list_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + </ListView> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/fragment_status.xml b/src/main/res/layout/fragment_status.xml new file mode 100644 index 0000000..65f63b1 --- /dev/null +++ b/src/main/res/layout/fragment_status.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Message Placeholder" + android:id="@+id/status_message" + style="@android:style/TextAppearance.DeviceDefault.Large" + tools:ignore="HardcodedText"/> + + <LinearLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" + > + + + <ScrollView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:autoText="true" + android:id="@+id/status_scrollview" + android:layout_weight="1"> + + <TextView + android:layout_height="match_parent" + android:layout_width="match_parent" + android:id="@+id/status_logmessages"/> + </ScrollView> + + <Button + android:drawableLeft="@drawable/cancel" + android:text="Close" + android:id="@+id/status_cancel" + android:layout_width="match_parent" + android:layout_height="50dp" + /> + + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/main.xml b/src/main/res/layout/main.xml new file mode 100644 index 0000000..64bf136 --- /dev/null +++ b/src/main/res/layout/main.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/main_fragment_status" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/pref.xml b/src/main/res/layout/pref.xml new file mode 100644 index 0000000..ff54538 --- /dev/null +++ b/src/main/res/layout/pref.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <ListView + android:id="@+id/pref_list" + android:layout_width="match_parent" + android:layout_height="match_parent" > + </ListView> + +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/layout/rowlayout.xml b/src/main/res/layout/rowlayout.xml new file mode 100644 index 0000000..7fd7f4e --- /dev/null +++ b/src/main/res/layout/rowlayout.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" > + + <LinearLayout + android:id="@+id/rowlayout_content" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="2" + android:orientation="vertical" > + + <TextView + android:id="@+id/rowlayout_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + <ImageButton + android:id="@+id/rowlayout_help" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_gravity="center" + android:background="@drawable/action_help" + android:contentDescription="Help" + tools:ignore="HardcodedText" /> + +</LinearLayout>
\ No newline at end of file diff --git a/src/main/res/menu/fragment_list.xml b/src/main/res/menu/fragment_list.xml new file mode 100644 index 0000000..44f54e9 --- /dev/null +++ b/src/main/res/menu/fragment_list.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/menu_main_add" + android:icon="@drawable/device_access_new_account" + android:showAsAction="always" + android:title="@string/fragment_list_add"> + </item> +</menu>
\ No newline at end of file diff --git a/src/main/res/menu/main.xml b/src/main/res/menu/main.xml new file mode 100644 index 0000000..4a179ff --- /dev/null +++ b/src/main/res/menu/main.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_main_about" + android:icon="@drawable/action_help" + android:showAsAction="never" + android:title="@string/main_about"> + </item> + +</menu>
\ No newline at end of file diff --git a/src/main/res/menu/pref.xml b/src/main/res/menu/pref.xml new file mode 100644 index 0000000..a73acea --- /dev/null +++ b/src/main/res/menu/pref.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/menu_pref_delete" + android:icon="@drawable/delete" + android:title="@string/pref_delete" + android:showAsAction="always"> + </item> + +</menu>
\ No newline at end of file diff --git a/src/main/res/raw/license b/src/main/res/raw/license new file mode 100644 index 0000000..1265d24 --- /dev/null +++ b/src/main/res/raw/license @@ -0,0 +1,56 @@ +== Andiodine iodine Android App: +Copyright (c) 2013 Yves Fischer <yvesf@xapek.org> +licensed under the same terms as iodine + +== iodine C-Implementation: (jni/iodine) +Copyright (c) 2006-2009 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se> +Also major contributions by Anne Bezemer. +Android modifications by Yves Fischer. + +http://code.kryo.se/iodine/ + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + + +== MD5 implementation (jni/iodine/src/md5.[ch]) +MD5 implementation by L. Peter Deutsch (license and source in src/md5.[ch]) +Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + +== dns definitions (jni/iodine/src/dns_android.h) + * Copyright (c) 1983, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE.
\ No newline at end of file diff --git a/src/main/res/raw/logo.svg b/src/main/res/raw/logo.svg new file mode 100644 index 0000000..e742823 --- /dev/null +++ b/src/main/res/raw/logo.svg @@ -0,0 +1,742 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + preserveAspectRatio="xMidYMid meet" + zoomAndPan="magnify" + viewBox="-800 -800 1000 1000" + width="1000" + height="1000" + id="svg2" + inkscape:version="0.48.5 r10040" + sodipodi:docname="logo.svg" + inkscape:export-filename="/home/yvesf/vcs/andiodine-github/res/drawable/logo.png" + inkscape:export-xdpi="11.52" + inkscape:export-ydpi="11.52"> + <metadata + id="metadata82"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1436" + inkscape:window-height="880" + id="namedview80" + showgrid="false" + inkscape:zoom="0.6675088" + inkscape:cx="478.53622" + inkscape:cy="487.65456" + inkscape:window-x="0" + inkscape:window-y="18" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <defs + id="defs6"> + <circle + id="electron" + r="30" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="30" + sodipodi:ry="30" + style="fill:#fafafa;stroke:#2f2f2f" /> + </defs> + <circle + d="m 200,0 c 0,110.45695 -89.54305,200 -200,200 -110.45695,0 -200,-89.54305 -200,-200 0,-110.45695 89.54305,-200 200,-200 110.45695,0 200,89.54305 200,200 z" + r="200" + id="circle3227-5" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="200" + sodipodi:ry="200" + style="fill:#ffffff;fill-opacity:1;stroke:#343434;stroke-width:2.26129237;stroke-miterlimit:4;stroke-dasharray:none" + transform="matrix(0.944522,0,0,0.94201149,-300,-300)" /> + <g + id="g3229-2" + transform="matrix(0.93986331,0,0,0.98484463,-298.58417,-403.20625)"> + <g + id="g3231-9" + style="fill:#a4c639"> + <g + transform="scale(-1,1)" + style="stroke:#ffffff;stroke-width:7.19999981" + id="use3233-3"> + <rect + style="stroke:none" + rx="6.5" + transform="matrix(0.87461971,0.48480962,-0.48480962,0.87461971,0,0)" + height="86" + width="13" + y="-86" + x="14" + id="rect3571" /> + <rect + style="stroke:none" + id="rect3573" + rx="24" + height="133" + width="48" + y="41" + x="-143" /> + <rect + style="stroke:none" + id="use3575" + rx="24" + height="133" + width="48" + y="138" + x="-58" /> + </g> + <g + id="g3235-5" + style="stroke:#ffffff;stroke-width:7.19999981"> + <rect + id="rect3237-7" + x="14" + y="-86" + width="13" + height="86" + transform="matrix(0.87461971,0.48480962,-0.48480962,0.87461971,0,0)" + rx="6.5" + style="stroke:none" /> + <rect + x="-143" + y="41" + width="48" + height="133" + rx="24" + id="rect3239-4" + style="stroke:none" /> + <rect + style="stroke:none" + id="use3241-5" + rx="24" + height="133" + width="48" + y="138" + x="-58" /> + </g> + <g + id="g3243-6"> + <ellipse + style="fill:#a4c639;fill-opacity:1;stroke:#a4c639;stroke-opacity:1" + id="ellipse3245-2" + ry="84" + rx="91" + cy="41" + cx="0" + sodipodi:cx="0" + sodipodi:cy="41" + sodipodi:rx="91" + sodipodi:ry="84" + d="m 91,41 c 0,46.391919 -40.742088,84 -91,84 -50.257912,0 -91,-37.608081 -91,-84 0,-46.391919 40.742088,-84 91,-84 50.257912,0 91,37.608081 91,84 z" /> + <path + inkscape:connector-curvature="0" + id="rect3247-7" + transform="matrix(1.2916604,0,0,1.2326657,-647.5919,-511.14118)" + d="m 447.9375,430.875 c -7.99936,0 -14.65225,5.73388 -16.5,13.5 l 139.84375,0 c -1.84879,-7.76454 -8.5017,-13.5 -16.5,-13.5 l -106.84375,0 z M 430.90625,451 l 0,109.6875 c 0,9.88751 7.59533,17.84375 17.03125,17.84375 l 106.84375,0 c 9.43592,0 17.03125,-7.95624 17.03125,-17.84375 l 0,-109.6875 -140.90625,0 z" /> + <path + id="path3251-2" + d="M -90.99955,40.88152 H 90.999549" + style="fill:#ffffff;stroke:#ffffff;stroke-width:7.04677343;display:inline" + inkscape:connector-curvature="0" /> + </g> + <g + transform="translate(-0.049281,4.3465664)" + id="g3249-3" + style="fill:#ffffff;stroke:#ffffff;stroke-width:7.19999981;display:inline"> + <circle + id="circle3253-6" + r="4" + cx="-42" + cy="0" + sodipodi:cx="-42" + sodipodi:cy="0" + sodipodi:rx="4" + sodipodi:ry="4" + d="m -38,0 c 0,2.209139 -1.790861,4 -4,4 -2.209139,0 -4,-1.790861 -4,-4 0,-2.209139 1.790861,-4 4,-4 2.209139,0 4,1.790861 4,4 z" /> + <circle + id="circle3255-6" + r="4" + cx="42" + cy="0" + sodipodi:cx="42" + sodipodi:cy="0" + sodipodi:rx="4" + sodipodi:ry="4" + d="m 46,0 c 0,2.209139 -1.790861,4 -4,4 -2.209139,0 -4,-1.790861 -4,-4 0,-2.209139 1.790861,-4 4,-4 2.209139,0 4,1.790861 4,4 z" /> + </g> + </g> + </g> + <g + id="g4283" + transform="matrix(1.0380709,0,0,1.0380709,11.421268,11.421265)"> + <circle + transform="matrix(0.73071782,0,0,0.73071782,-300,-291.26788)" + id="shell_1" + r="300" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="300" + sodipodi:ry="300" + d="M 300,0 C 300,165.68542 165.68542,300 0,300 -165.68542,300 -300,165.68542 -300,0 c 0,-165.68542 134.31458,-300 300,-300 165.68542,0 300,134.31458 300,300 z" + style="fill:none;stroke:#77868b;stroke-width:8" /> + <use + transform="matrix(0.73071782,0,0,0.73071782,-300,-291.26788)" + id="electron_0_shell_1" + xlink:href="#electron" + y="-300" + x="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_1_shell_1" + xlink:href="#electron_0_shell_1" + transform="matrix(-1,0,0,-1,-600,-582.53576)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <circle + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="shell_2" + r="400" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="400" + sodipodi:ry="400" + d="M 400,0 C 400,220.9139 220.9139,400 0,400 -220.9139,400 -400,220.9139 -400,0 c 0,-220.9139 179.0861,-400 400,-400 220.9139,0 400,179.0861 400,400 z" + style="fill:none;stroke:#77868b;stroke-width:8" /> + <use + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="electron_0_shell_2" + xlink:href="#electron" + y="-400" + x="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_1_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,-293.82546,126.82165)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_2_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(0,1,-1,0,-591.26788,8.73212)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_3_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(-0.70710678,0.70710678,-0.70710678,-0.70710678,-718.08953,-285.09334)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_4_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(-1,0,0,-1,-600,-582.53576)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_5_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(-0.70710678,-0.70710678,0.70710678,-0.70710678,-306.17454,-709.35741)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_6_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(0,-1,1,0,-8.73212,-591.26788)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_7_shell_2" + xlink:href="#electron_0_shell_2" + transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,118.08953,-297.44242)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <circle + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="shell_3" + r="500" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="500" + sodipodi:ry="500" + d="M 500,0 C 500,276.14237 276.14237,500 0,500 -276.14237,500 -500,276.14237 -500,0 c 0,-276.14237 223.85763,-500 500,-500 276.14237,0 500,223.85763 500,500 z" + style="fill:none;stroke:#77868b;stroke-width:8" /> + <use + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="electron_0_shell_3" + xlink:href="#electron" + y="-500" + x="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_1_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.93969262,0.34202014,-0.34202014,0.93969262,-117.7117,85.040439)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_2_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.76604444,0.64278761,-0.64278761,0.76604444,-257.41005,124.69254)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_3_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.5,0.8660254,-0.8660254,0.5,-402.24538,114.17368)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_4_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.17364818,0.98480775,-0.98480775,0.17364818,-534.74841,54.752582)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_5_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.17364818,0.98480775,-0.98480775,-0.17364818,-638.93732,-46.403692)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_6_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.5,0.8660254,-0.8660254,-0.5,-702.24538,-177.0942)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_7_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.76604444,0.64278761,-0.64278761,-0.76604444,-717.03672,-321.55574)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_8_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.93969262,0.34202014,-0.34202014,-0.93969262,-681.52727,-462.36412)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_9_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-1,0,0,-1,-600,-582.53576)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_10_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.93969262,-0.34202014,0.34202014,-0.93969262,-482.2883,-667.5762)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_11_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.76604444,-0.64278761,0.64278761,-0.76604444,-342.58995,-707.2283)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_12_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.5,-0.8660254,0.8660254,-0.5,-197.75462,-696.70944)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_13_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(-0.17364818,-0.98480775,0.98480775,-0.17364818,-65.251588,-637.28834)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_14_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.17364818,-0.98480775,0.98480775,0.17364818,38.93732,-536.13207)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_15_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.5,-0.8660254,0.8660254,0.5,102.24538,-405.44156)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_16_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.76604444,-0.64278761,0.64278761,0.76604444,117.03672,-260.98002)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_17_shell_3" + xlink:href="#electron_0_shell_3" + transform="matrix(0.93969262,-0.34202014,0.34202014,0.93969262,81.527267,-120.17164)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <circle + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="shell_4" + r="600" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="600" + sodipodi:ry="600" + d="M 600,0 C 600,331.37085 331.37085,600 0,600 -331.37085,600 -600,331.37085 -600,0 c 0,-331.37085 268.62915,-600 600,-600 331.37085,0 600,268.62915 600,600 z" + style="fill:none;stroke:#77868b;stroke-width:8" /> + <use + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="electron_0_shell_4" + xlink:href="#electron" + y="-600" + x="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_1_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.93969262,0.34202014,-0.34202014,0.93969262,-117.7117,85.040439)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_2_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.76604444,0.64278761,-0.64278761,0.76604444,-257.41005,124.69254)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:9.6332534;stroke-miterlimit:4;stroke-dasharray:none" /> + <use + id="electron_3_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.5,0.8660254,-0.8660254,0.5,-402.24538,114.17368)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_4_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.17364818,0.98480775,-0.98480775,0.17364818,-534.74841,54.752582)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_5_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.17364818,0.98480775,-0.98480775,-0.17364818,-638.93732,-46.403692)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_6_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.5,0.8660254,-0.8660254,-0.5,-702.24538,-177.0942)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_7_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.76604444,0.64278761,-0.64278761,-0.76604444,-717.03672,-321.55574)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_8_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.93969262,0.34202014,-0.34202014,-0.93969262,-681.52727,-462.36412)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_9_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-1,0,0,-1,-600,-582.53576)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_10_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.93969262,-0.34202014,0.34202014,-0.93969262,-482.2883,-667.5762)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_11_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.76604444,-0.64278761,0.64278761,-0.76604444,-342.58995,-707.2283)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_12_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.5,-0.8660254,0.8660254,-0.5,-197.75462,-696.70944)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_13_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(-0.17364818,-0.98480775,0.98480775,-0.17364818,-65.251588,-637.28834)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_14_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.17364818,-0.98480775,0.98480775,0.17364818,38.93732,-536.13207)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_15_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.5,-0.8660254,0.8660254,0.5,102.24538,-405.44156)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_16_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.76604444,-0.64278761,0.64278761,0.76604444,117.03672,-260.98002)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_17_shell_4" + xlink:href="#electron_0_shell_4" + transform="matrix(0.93969262,-0.34202014,0.34202014,0.93969262,81.527267,-120.17164)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <circle + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" + id="shell_5" + r="700" + cx="0" + cy="0" + sodipodi:cx="0" + sodipodi:cy="0" + sodipodi:rx="700" + sodipodi:ry="700" + d="M 700,0 C 700,386.59932 386.59932,700 0,700 -386.59932,700 -700,386.59932 -700,0 c 0,-386.59932 313.40068,-700 700,-700 386.59932,0 700,313.40068 700,700 z" + style="fill:none;stroke:#77868b;stroke-width:8" /> + <a + id="a4147" + style="fill:none;stroke:none;fill-opacity:1"> + <use + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" + height="1600" + width="1600" + x="0" + y="-700" + xlink:href="#electron" + id="electron_0_shell_5" + transform="matrix(0.67170146,0,0,0.67170146,-300,-291.26788)" /> + </a> + <use + id="electron_1_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(0.62348981,0.78183148,-0.78183148,0.62348981,-340.67545,124.88412)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_2_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(-0.22252094,0.97492791,-0.97492791,-0.22252094,-650.72147,-63.602709)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_3_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(-0.90096887,0.43388374,-0.43388374,-0.90096887,-696.66706,-423.52605)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_4_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(-0.90096887,-0.43388374,0.43388374,-0.90096887,-443.91426,-683.85629)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_5_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(-0.22252094,-0.97492791,0.97492791,-0.22252094,-82.791097,-648.55946)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + <use + id="electron_6_shell_5" + xlink:href="#electron_0_shell_5" + transform="matrix(0.62348981,-0.78183148,0.78183148,0.62348981,114.76934,-344.21477)" + x="0" + y="0" + width="1600" + height="1600" + style="fill:none;fill-opacity:1;stroke:none;stroke-width:8" /> + </g> +</svg> diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..9b72d05 --- /dev/null +++ b/src/main/res/values-de/strings.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">AndIodine</string> + <string name="app_about">Über</string> + + <string-array name="pref_entries_dnstype"> + <item>automatisch</item> + <item>NULL</item> + <item>TXT</item> + <item>SRV</item> + <item>MX</item> + <item>CNAME</item> + <item>A</item> + </string-array> + <string-array name="pref_entries_nameserver_mode"> + <item>Netzstandard</item> + <item>Brauch</item> + <item>Tunnel-Endpunkt</item> + </string-array> + + <string name="main_description_add_account">Konto hinzufügen</string> + <string name="pref_description_cancel">Rückgängig Machen</string> + <string name="configitem_description_close">Schließen Tunnel</string> + <string name="pref_text_password_label">Passwort</string> + <string name="pref_text_topdomain_label">Tunnel Toplevel Domain</string> + <string name="pref_text_tunnel_nameserver_label">Tunnel Nameserver</string> + <string name="pref_text_nameserver_mode_label">Nameserver Modus</string> + <string name="pref_text_nameserver_address_label">Nameserver Adresse</string> + <string name="pref_text_name_label">Profil Name</string> + <string name="pref_text_use_default_nameserver">Verwende standard Nameserver</string> + <string name="pref_help_name">Name für diese Verbindungskonfiguration</string> + <string name="pref_help_topdomain">Der DNS Traffic wird als Anfragen für subdomains unterhalb dieser Topdomain gesendert. Dies ist gewöhnlich eine Domain die Dir gehört. Verwende eine kurze Domain um mehr Durchsatz zu erzielen. Diese Einstellung muss am Server und am Client gleich sein</string> + <string name="pref_help_lazy">Verwende lazy mode für bessere performance und niedrigere latenzzeiten. Eine kleine Minderheit an DNS Relays scheinen damit nicht klarzukommen was darin resultiert dass keine oder fast keine Daten übertragen werden. Der Client wird dies aber in der Regel feststellen und den Lazy mode ausschalten. Falls nicht verwende diese Option</string> + <string name="pref_help_raw">Falls gesetzt wird iodine versuchen die öffentliche IP-Adresse des iodined Server aufzulösen und testen ob er direkt erreichbar ist. Falls ja, wird er den Traffic direkt an den Server senden anstatt an ein DNS relay</string> + <string name="pref_help_tunnel_nameserver">Der Nameserver/DNS Relay der verwendet wird um mit iodined zu kommunizieren. Dieses Feld ist optional und wenn es nicht gesetzt ist wird der im System hinterlegte DNS Server verwendet</string> + <string name="pref_help_password">Dieses Feld ist optional. Es werden nur die ersten 32 Zeichen verwendet.</string> + <string name="pref_help_nameserver_mode">Legt fest wie der Nameserver gesetzt werden soll nachdem der Tunnel aufgebaut wurde</string> + <string name="pref_help_nameserver">IP-Adresse eine speziellen Nameserver der gesetzt werden soll wenn Nameserver Modus = Custom ist.</string> + <string name="pref_help_request_type">Typ der DNS Abfragen. Standardmäßig wird die beste Request type automatisch ausgewählt.</string> + <string name="pref_help_default_route">Legt fest ob die Default Route gesetzt wird nachdem die Verbindung aufgebaut wurde</string> + <string name="enable">Aktivieren</string> + <string name="vpnservice_error_configuration_incomplete">Konfiguration ist unvollständig</string> + <string name="vpnservice_error_already_running">Kann das VPN nicht starten, es läuft bereits.</string> + <string name="vpnservice_error_thread_exited">VPN Thread hat sich unerwartet beendet.</string> + <string name="vpnservice_error_cant_open_dnssocket">Kann den DNS Socket nicht öffnen</string> + <string name="vpnservice_error_handshake_failed">Handshake fehlgeschlagen</string> + <string name="vpnservice_error_unknown_error_code">Unbekannter Fehler. Kode %d</string> + <string name="vpnservice_error_unknown_error_string">Unbekannter Fehler. Grund: %s</string> + <string name="idle">Leerlauf</string> + <string name="warning">Warnung</string> + <string name="error">Fehler</string> + <string name="configitem_description_manage">Konfiguriere dieses Verbindungsprofil</string> + <string name="pref_title">Verbindungseinstellungen</string> + <string name="main_create_tunnel">Erstellen Sie DNS-Tunnel von Configuration Namen %s</string> + <string name="fragment_list_add">Neuer Kunde</string> + <string name="main_about">Über</string> + <string name="pref_delete">Löschen</string> + +</resources> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml new file mode 100644 index 0000000..ffbbb15 --- /dev/null +++ b/src/main/res/values/strings.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">AndIodine</string> + <string name="app_about">About</string> + + <string-array name="pref_entries_dnstype"> + <item>autodetect</item> + <item>NULL</item> + <item>TXT</item> + <item>SRV</item> + <item>MX</item> + <item>CNAME</item> + <item>A</item> + </string-array> + <string-array name="pref_entries_nameserver_mode"> + <item>Network default</item> + <item>Custom</item> + <item>Tunnel Endpoint</item> + </string-array> + + <string name="main_description_add_account">Add Account</string> + <string name="pref_description_cancel">Cancel</string> + <string name="configitem_description_close">Close Tunnel</string> + <string name="pref_text_password_label">Password</string> + <string name="pref_text_topdomain_label">Tunnel Toplevel Domain</string> + <string name="pref_text_tunnel_nameserver_label">Tunnel Nameserver</string> + <string name="pref_text_nameserver_mode_label">Nameserver Mode</string> + <string name="pref_text_nameserver_address_label">Nameserver Address</string> + <string name="pref_text_name_label">Configuration Profile Name</string> + <string name="pref_text_use_default_nameserver">Use default Nameserver for tunneling</string> + <string name="pref_help_name">Name for this configuration.</string> + <string name="pref_help_topdomain">The dns traffic will be sent as queries for subdomains under _topdomain_. This is + normally a subdomain to a domain you own. Use a short domain name to get better throughput. If nameserver is the + iodined server, then the topdomain can be chosen freely. This argument must be the same on both the client and + the server. + </string> + <string name="pref_help_lazy">Use lazy mode for improved performance and decreased latency. A very small minority of + DNS relays appears to be unable to handle the lazy mode traffic pattern, resulting in no or very little data + coming through. The iodine client will detect this and try to switch back to legacy mode, but this may not + always work. In these situations use this switch. + </string> + <string name="pref_help_raw">If set used, iodine will try getting the public IP address of the iodined host and test + if it is reachable directly. If it is, traffic will be sent to the server instead of the DNS relay. + </string> + <string name="pref_help_tunnel_nameserver">The nameserver to use to relay the dns traffic. This can be any relaying + nameserver or the server running iodined if reachable. This field can be given as an IP address, or as a + hostname. This argument is optional, and if not specified the default system Nameserver will be used. + </string> + <string name="pref_help_password">This field is optional. Only the first 32 characters will be used</string> + <string name="pref_help_nameserver_mode">Source of the Nameserver to use for name resolution while tunnel is + active + </string> + <string name="pref_help_nameserver">Custom Nameserver IP-Address to set if Nameserver-mode is "Custom"</string> + <string name="pref_help_request_type">DNS request type override. By default, autodetection will probe for working + DNS request types, and will select the request type that is expected to provide the most bandwidth. However, it + may turn out that a DNS relay imposes limits that skew the picture, which may lead to an "unexpected" DNS + request type providing more bandwidth. In that case, use this option to override the autodetection. In + (expected) decreasing bandwidth order, the supported DNS request types are: NULL, TXT, SRV, MX, CNAME and A + (returning CNAME). Note that SRV, MX and A may/will cause additional lookups by "smart" caching nameservers to + get an actual IP address, which may either slow down or fail completely. + </string> + <string name="enable">Enable</string> + <string name="vpnservice_error_configuration_incomplete">Configuration is incomplete</string> + <string name="vpnservice_error_already_running">Cannot start VPN Service, VPN-Thread is already running</string> + <string name="vpnservice_error_thread_exited">VPN Thread exited unexpected</string> + <string name="vpnservice_error_cant_open_dnssocket">Can\'t open DNS Socket</string> + <string name="vpnservice_error_handshake_failed">Handshake failed</string> + <string name="vpnservice_error_unknown_error_code">Unknown Error. Code %d</string> + <string name="vpnservice_error_unknown_error_string">Unknown Error. Reason: %s</string> + <string name="idle">Idle</string> + <string name="warning">Warning</string> + <string name="error">Error</string> + <string name="configitem_description_manage">Configure this Connection Profile</string> + <string name="pref_title">Connection Configuration</string> + <string name="pref_help_default_route">Set default Route for the tunnel device</string> + <string name="main_create_tunnel">Create DNS Tunnel from Configuration named %s</string> + <string name="fragment_list_add">New Account</string> + <string name="main_about">About</string> + <string name="pref_delete">Delete</string> + +</resources> |