├── .gitignore ├── assets └── iodine ├── res ├── drawable │ ├── logo.png │ ├── check_nok.png │ ├── check_ok.png │ └── checkbox.xml ├── drawable-hdpi │ └── icon.png ├── drawable-ldpi │ └── icon.png ├── drawable-mdpi │ └── icon.png ├── xml │ ├── checklist.xml │ ├── tunnellist.xml │ └── tunnelsettings.xml ├── menu │ ├── tunnellistpref_disconnect.xml │ └── tunnellistpref_context.xml ├── layout │ ├── checkbox.xml │ ├── tunnelslistitem.xml │ ├── tunnelslistitemseparator.xml │ ├── settingslistitem.xml │ ├── log.xml │ ├── main.xml │ └── aboutbox.xml └── values │ └── strings.xml ├── .classpath ├── project.properties ├── src └── net │ └── magictunnel │ ├── settings │ ├── DnsRawConnection.java │ ├── DnsProtocol.java │ ├── Settings.java │ └── Profile.java │ ├── core │ ├── ITunnelStatusListener.java │ ├── IodineException.java │ ├── PartitionInfo.java │ ├── Tunnel.java │ ├── ConnectivityListener.java │ ├── Partition.java │ ├── Commands.java │ ├── Installer.java │ ├── RouteEntry.java │ ├── NetworkUtils.java │ └── Iodine.java │ ├── MagicTunnel.java │ ├── Log.java │ ├── Utils.java │ ├── SystemComponentChecklist.java │ ├── old │ └── TunnelListActivity.java │ ├── TunnelListPreferences.java │ └── TunnelPreferences.java ├── ant.properties ├── .project └── AndroidManifest.xml /.gitignore: -------------------------------------------------------------------------------- 1 | gen 2 | bin 3 | proguard.cfg 4 | *.apk 5 | -------------------------------------------------------------------------------- /assets/iodine: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/assets/iodine -------------------------------------------------------------------------------- /res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable/logo.png -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/drawable/check_nok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable/check_nok.png -------------------------------------------------------------------------------- /res/drawable/check_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicTunnel/MagicTunnelAndroid/HEAD/res/drawable/check_ok.png -------------------------------------------------------------------------------- /res/xml/checklist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /res/xml/tunnellist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /res/xml/tunnelsettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/menu/tunnellistpref_disconnect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /res/layout/checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /res/layout/tunnelslistitem.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /res/layout/tunnelslistitemseparator.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | -------------------------------------------------------------------------------- /res/menu/tunnellistpref_context.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /res/drawable/checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/net/magictunnel/settings/DnsRawConnection.java: -------------------------------------------------------------------------------- 1 | package net.magictunnel.settings; 2 | 3 | /** 4 | * Whether or not to use raw connection for Dns tunneling or 5 | * to use automatic detection instead. 6 | * @author Vitaly 7 | * 8 | */ 9 | public enum DnsRawConnection { 10 | /** Autodetect optimal setting. */ 11 | AUTODETECT, 12 | 13 | /** Use raw connection. */ 14 | YES, 15 | 16 | /** Do not use raw connection. */ 17 | NO 18 | } 19 | -------------------------------------------------------------------------------- /res/layout/settingslistitem.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/net/magictunnel/settings/DnsProtocol.java: -------------------------------------------------------------------------------- 1 | package net.magictunnel.settings; 2 | 3 | /** 4 | * Type of protocol to use for data transfer. 5 | * @author Vitaly 6 | * 7 | */ 8 | public enum DnsProtocol { 9 | /** Automatically detect the optimal protocol. */ 10 | AUTODETECT, 11 | 12 | /** Use the NULL protocol. */ 13 | NULL, 14 | 15 | /** Pack data in TXT records. */ 16 | TXT, 17 | 18 | /** Pack data in SRV records. */ 19 | SRV, 20 | 21 | /** Pack data in MX records. */ 22 | MX, 23 | 24 | /** Pack data in CNAME records. */ 25 | CNAME, 26 | 27 | /** Pack data in A records. */ 28 | A 29 | } 30 | -------------------------------------------------------------------------------- /res/layout/log.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked in Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | key.alias=magictunnel 19 | key.store=../android-keys/magictunnel.keystore 20 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | MagicTunnel 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | net.sf.eclipsecs.core.CheckstyleBuilder 30 | 31 | 32 | 33 | 34 | 35 | com.android.ide.eclipse.adt.AndroidNature 36 | org.eclipse.jdt.core.javanature 37 | net.sf.eclipsecs.core.CheckstyleNature 38 | 39 | 40 | -------------------------------------------------------------------------------- /res/layout/aboutbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/ITunnelStatusListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | /** 22 | * Interface for tunnel-related callbacks. 23 | * They must be all called in the UI thread context. 24 | * @author vitaly 25 | * 26 | */ 27 | public interface ITunnelStatusListener { 28 | /** 29 | * Triggered when a tunnel is set up. 30 | * @param name The name of the tunnel. 31 | */ 32 | void onTunnelConnect(String name); 33 | 34 | /** 35 | * Triggered when a tunnel is disconnected. 36 | * @param name The name of the tunnel. 37 | */ 38 | void onTunnelDisconnect(String name); 39 | } 40 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/IodineException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | /** 22 | * Class that represents an Iodine exception. 23 | * @author Vitaly 24 | * 25 | */ 26 | public class IodineException extends Exception { 27 | 28 | /** Serial number. */ 29 | private static final long serialVersionUID = -4144280992818031303L; 30 | 31 | /** The resource id of the message. */ 32 | private int mMsgResId; 33 | 34 | 35 | /** 36 | * Creates a new Iodine exception with the specified message id. 37 | * @param msgResId The message id. 38 | */ 39 | public IodineException(final int msgResId) { 40 | mMsgResId = msgResId; 41 | } 42 | 43 | /** 44 | * Get the message id. 45 | * @return The message id of the exception. 46 | */ 47 | public final int getMessageId() { 48 | return mMsgResId; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/net/magictunnel/MagicTunnel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import net.magictunnel.core.ConnectivityListener; 22 | import net.magictunnel.core.Iodine; 23 | import net.magictunnel.settings.Settings; 24 | import android.app.Application; 25 | 26 | /** 27 | * Stores application-wide data. 28 | * @author Vitaly 29 | * 30 | */ 31 | public class MagicTunnel extends Application { 32 | 33 | /** The iodine client. */ 34 | private Iodine mIodine; 35 | 36 | /** Connection listener. */ 37 | @SuppressWarnings("unused") 38 | private ConnectivityListener mListener; 39 | 40 | 41 | @Override 42 | public final void onCreate() { 43 | super.onCreate(); 44 | mIodine = new Iodine(); 45 | mListener = new ConnectivityListener(this, mIodine); 46 | } 47 | 48 | /** 49 | * Get the settings. 50 | * @return the settings. 51 | */ 52 | public final Settings getSettings() { 53 | return Settings.get(getApplicationContext()); 54 | } 55 | 56 | /** 57 | * Get the iodine instance. 58 | * @return the iodine instance. 59 | */ 60 | public final Iodine getIodine() { 61 | return mIodine; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/PartitionInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | /** 22 | * Models a partition table entry. 23 | * @author Vitaly 24 | * 25 | */ 26 | public class PartitionInfo { 27 | /** 28 | * Name of the device (e.g., /dev/partition). 29 | */ 30 | private String mDevice; 31 | 32 | /** 33 | * Mount point location (e.g., /mnt/partition). 34 | */ 35 | private String mMountPoint; 36 | 37 | /** 38 | * File system type. 39 | */ 40 | private String mType; 41 | 42 | 43 | 44 | /** 45 | * Get the name of the device. 46 | * @return The device name. 47 | */ 48 | public final String getDevice() { 49 | return mDevice; 50 | } 51 | 52 | /** 53 | * Set the name of the device. 54 | * @param device The device name. 55 | */ 56 | public final void setDevice(final String device) { 57 | this.mDevice = device; 58 | } 59 | 60 | /** 61 | * Get the mount point. 62 | * @return The mount point. 63 | */ 64 | public final String getMountPoint() { 65 | return mMountPoint; 66 | } 67 | 68 | /** 69 | * Set the mount point. 70 | * @param mountPoint The mount point. 71 | */ 72 | public final void setMountPoint(final String mountPoint) { 73 | this.mMountPoint = mountPoint; 74 | } 75 | 76 | /** 77 | * Get the partition type. 78 | * @return The partition type. 79 | */ 80 | public final String getType() { 81 | return mType; 82 | } 83 | 84 | /** 85 | * Set the partition type. 86 | * @param type The partition type. 87 | */ 88 | public final void setType(final String type) { 89 | this.mType = type; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/net/magictunnel/Log.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import net.magictunnel.core.Iodine; 22 | import android.app.Activity; 23 | import android.os.Bundle; 24 | import android.text.ClipboardManager; 25 | import android.text.method.ScrollingMovementMethod; 26 | import android.view.Menu; 27 | import android.view.MenuItem; 28 | import android.widget.TextView; 29 | 30 | /** 31 | * Displays the log to the user for debugging purposes. 32 | * @author Vitaly 33 | * 34 | */ 35 | public class Log extends Activity { 36 | 37 | /** Menu id for copying the log to the clipboard. */ 38 | private static final int MENU_COPYLOG = Menu.FIRST; 39 | 40 | /** 41 | * Called when the activity is first created. 42 | * @param savedInstanceState saved state. 43 | */ 44 | @Override 45 | public final void onCreate(final Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | 48 | setContentView(R.layout.log); 49 | 50 | MagicTunnel mt = ((MagicTunnel) getApplication()); 51 | Iodine iod = mt.getIodine(); 52 | 53 | TextView textview = (TextView) findViewById(R.id.log_view); 54 | textview.setMovementMethod(new ScrollingMovementMethod()); 55 | String s = iod.getLog().toString(); 56 | if (s.length() == 0) { 57 | textview.setText(R.string.log_empty); 58 | } else { 59 | textview.setText(iod.getLog().toString()); 60 | } 61 | } 62 | 63 | @Override 64 | public final boolean onCreateOptionsMenu(final Menu menu) { 65 | super.onCreateOptionsMenu(menu); 66 | menu.add(0, MENU_COPYLOG, 0, R.string.log_copy).setIcon( 67 | android.R.drawable.ic_menu_share); 68 | return true; 69 | } 70 | 71 | @Override 72 | public final boolean onOptionsItemSelected(final MenuItem item) { 73 | 74 | switch (item.getItemId()) { 75 | case MENU_COPYLOG: 76 | ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 77 | MagicTunnel mt = ((MagicTunnel) getApplication()); 78 | Iodine iod = mt.getIodine(); 79 | clipboard.setText(iod.getLog().toString()); 80 | break; 81 | 82 | default: 83 | return false; 84 | } 85 | 86 | return true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/Tunnel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.InputStreamReader; 25 | import java.util.zip.GZIPInputStream; 26 | 27 | /** 28 | * This class contains methods for detection of the TAP driver. 29 | * 30 | * @author Vitaly 31 | * 32 | */ 33 | public final class Tunnel { 34 | 35 | /** 36 | * The kernel configuration key to search for. 37 | */ 38 | private static final String CONFIG_TUN = "CONFIG_TUN"; 39 | 40 | /** 41 | * The location of the kernel configuration file. 42 | */ 43 | private static final String CONFIG = "/proc/config.gz"; 44 | 45 | /** 46 | * The location of the TAP driver's device file. 47 | */ 48 | private static final String DEV_TUN_FILE = "/dev/tun"; 49 | 50 | /** 51 | * This class is not supposed to be instantiated. 52 | */ 53 | private Tunnel() { 54 | 55 | } 56 | 57 | /** 58 | * Checks the presence of TAP device using various methods. 59 | * 60 | * @return Whether the TAP device is present or not. 61 | */ 62 | public static boolean checkTap() { 63 | if (checkTunDevice()) { 64 | return true; 65 | } 66 | 67 | if (checkConfigGz()) { 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | /** 75 | * Looks for /dev/tun. 76 | * 77 | * @return if /dev/tun exists 78 | */ 79 | private static boolean checkTunDevice() { 80 | File f = new File(DEV_TUN_FILE); 81 | return f.exists(); 82 | } 83 | 84 | /** 85 | * Look for CONFIG_TUN=y or CONFIG_TUN=m in /proc/config.gz. 86 | * 87 | * @return the presence of the kernel configuration option. 88 | */ 89 | public static boolean checkConfigGz() { 90 | File proc = new File(CONFIG); 91 | if (!proc.exists()) { 92 | return false; 93 | } 94 | 95 | try { 96 | GZIPInputStream procFile = new GZIPInputStream(new FileInputStream( 97 | proc)); 98 | BufferedReader buf = new BufferedReader(new InputStreamReader( 99 | procFile)); 100 | String line; 101 | 102 | while ((line = buf.readLine()) != null) { 103 | System.out.println(line); 104 | if (!line.contains(CONFIG_TUN)) { 105 | continue; 106 | } 107 | 108 | String [] s = line.split("="); 109 | if (s.length < 2) { 110 | continue; 111 | } 112 | 113 | if (s[1].equals("y") || s[1].equals("m")) { 114 | return true; 115 | } 116 | } 117 | } catch (Exception e) { 118 | return false; 119 | } 120 | 121 | return false; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/net/magictunnel/Utils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import android.app.AlertDialog; 22 | import android.app.Dialog; 23 | import android.content.ActivityNotFoundException; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.net.Uri; 27 | import android.text.method.LinkMovementMethod; 28 | import android.view.View; 29 | import android.view.View.OnClickListener; 30 | import android.widget.TextView; 31 | 32 | /** 33 | * Various utilities for the GUI. 34 | * @author Vitaly 35 | * 36 | */ 37 | public final class Utils { 38 | 39 | /** The class is not meant to be instantiated. */ 40 | private Utils() { 41 | 42 | } 43 | 44 | /** 45 | * Shows an error message. 46 | * @param c The Android context. 47 | * @param message The message to show. 48 | */ 49 | public static void showErrorMessage(final Context c, final String message) { 50 | AlertDialog.Builder b = new AlertDialog.Builder(c) 51 | .setTitle(android.R.string.dialog_alert_title) 52 | .setIcon(android.R.drawable.ic_dialog_alert) 53 | .setMessage(message); 54 | 55 | b.setPositiveButton(android.R.string.ok, null); 56 | b.create(); 57 | b.show(); 58 | } 59 | 60 | /** 61 | * Shows an error message using resource identifiers. 62 | * @param c The context 63 | * @param title The resource ID of the title. 64 | * @param message The resource ID of the message. 65 | */ 66 | public static void showErrorMessage( 67 | final Context c, 68 | final int title, 69 | final int message) { 70 | 71 | AlertDialog.Builder b = new AlertDialog.Builder(c) 72 | .setTitle(title) 73 | .setIcon(android.R.drawable.ic_dialog_alert) 74 | .setMessage(message); 75 | 76 | b.setPositiveButton(android.R.string.ok, null); 77 | b.create(); 78 | b.show(); 79 | } 80 | 81 | /** 82 | * Displays the Magictunnel about box. 83 | * @param c The context. 84 | */ 85 | public static void showAboutBox(final Context c) { 86 | Dialog dialog = new Dialog(c); 87 | dialog.setContentView(R.layout.aboutbox); 88 | TextView tv = (TextView) dialog.findViewById(R.id.about_url); 89 | tv.setMovementMethod(LinkMovementMethod.getInstance()); 90 | tv.setOnClickListener(new OnClickListener() { 91 | @Override 92 | public void onClick(final View v) { 93 | String url = v.getContext().getString(R.string.about_url); 94 | Intent intent = 95 | new Intent("android.intent.action.VIEW", Uri.parse(url)); 96 | 97 | try { 98 | v.getContext().startActivity(intent); 99 | } catch (ActivityNotFoundException ex) { 100 | // do something about the exception, or not ... 101 | return; 102 | } 103 | 104 | } 105 | }); 106 | dialog.show(); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/ConnectivityListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import net.magictunnel.settings.Profile; 22 | import android.content.BroadcastReceiver; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.content.IntentFilter; 26 | import android.net.wifi.WifiManager; 27 | import android.telephony.PhoneStateListener; 28 | import android.telephony.TelephonyManager; 29 | 30 | /** 31 | * Monitors connectivity changes, kills the dns tunnel 32 | * and resets routes accordingly. 33 | * @author Vitaly 34 | * 35 | */ 36 | public class ConnectivityListener { 37 | 38 | /** The iodine client. */ 39 | private Iodine mIodine; 40 | 41 | /** 42 | * Create a new connectivity listener. 43 | * @param ctx The Android context. 44 | * @param iodine The iodine client. 45 | */ 46 | public ConnectivityListener(final Context ctx, final Iodine iodine) { 47 | mIodine = iodine; 48 | setupPhoneStateListener(ctx); 49 | setupWifiStateListener(ctx); 50 | } 51 | 52 | /** 53 | * XXX: Move to a separate class??? 54 | * @param ctx The Android context. 55 | */ 56 | private void setupPhoneStateListener(final Context ctx) { 57 | TelephonyManager telephonyManager = 58 | (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE); 59 | 60 | // Create a new PhoneStateListener 61 | PhoneStateListener phoneStateListener = new PhoneStateListener() { 62 | @Override 63 | public void onDataConnectionStateChanged(final int state) { 64 | Profile p = mIodine.getActiveProfile(); 65 | if (p == null) { 66 | mIodine.resetSavedRoutes(); 67 | return; 68 | } 69 | mIodine.resetSavedRoutes(); 70 | } 71 | }; 72 | 73 | telephonyManager.listen(phoneStateListener, 74 | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE); 75 | } 76 | 77 | /** 78 | * Setup wifi state listener. 79 | * @param ctx The Android context. 80 | */ 81 | private void setupWifiStateListener(final Context ctx) { 82 | 83 | BroadcastReceiver receiver = new BroadcastReceiver() { 84 | @Override 85 | public void onReceive(final Context context, final Intent intent) { 86 | int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 87 | WifiManager.WIFI_STATE_UNKNOWN); 88 | 89 | if (state != WifiManager.WIFI_STATE_DISABLING 90 | && state != WifiManager.WIFI_STATE_DISABLED) { 91 | return; 92 | } 93 | 94 | Profile p = mIodine.getActiveProfile(); 95 | 96 | if (p == null) { 97 | mIodine.resetSavedRoutes(); 98 | return; 99 | } 100 | mIodine.resetSavedRoutes(); 101 | mIodine.disconnect(); 102 | } 103 | }; 104 | 105 | IntentFilter intentfilter = new IntentFilter(); 106 | intentfilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 107 | intentfilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 108 | 109 | ctx.getApplicationContext(). 110 | registerReceiver(receiver, intentfilter); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/Partition.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.InputStreamReader; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | 28 | /** 29 | * Utility functions to remount system partition. 30 | * @author vitaly 31 | * 32 | */ 33 | public class Partition { 34 | 35 | /** 36 | * The place where the kernel stores info about mounted partitions. 37 | */ 38 | private static final String PARTITIONS_FILE = "/proc/mounts"; 39 | 40 | /** 41 | * The number of fields in each line of PARTITIONS_FILE. 42 | */ 43 | private static final int FIELDS_PER_ENTRY = 3; 44 | 45 | /** 46 | * The mapping from the mount point to the partition table entry. 47 | */ 48 | private Map mPartitions; 49 | 50 | /** 51 | * Constructs and initializes the partition manager with 52 | * the partitions found in the system. 53 | */ 54 | public Partition() { 55 | BufferedReader partFile = getPartitionFile(); 56 | if (partFile == null) { 57 | return; 58 | } 59 | 60 | mPartitions = readPartitions(partFile); 61 | } 62 | 63 | /** 64 | * Opens the partition file. 65 | * @return A means of reading the contents of the partition file. 66 | */ 67 | private BufferedReader getPartitionFile() { 68 | try { 69 | File file = new File(PARTITIONS_FILE); 70 | FileInputStream fis = new FileInputStream(file); 71 | BufferedReader buf = new BufferedReader(new InputStreamReader(fis)); 72 | return buf; 73 | } catch (Exception e) { 74 | return null; 75 | } 76 | } 77 | 78 | /** 79 | * Transforms the text contained in the partition file into 80 | * PartitionInfo objects. 81 | * @param file The partition file. 82 | * @return A mapping from mount point to PartitionInfo. 83 | */ 84 | private Map readPartitions( 85 | final BufferedReader file) { 86 | 87 | Map partitions = 88 | new HashMap(); 89 | 90 | String line; 91 | 92 | try { 93 | while ((line = file.readLine()) != null) { 94 | String[] s = line.split(" "); 95 | if (s.length < FIELDS_PER_ENTRY) { 96 | continue; 97 | } 98 | PartitionInfo info = new PartitionInfo(); 99 | info.setDevice(s[0]); 100 | info.setMountPoint(s[1]); 101 | info.setType(s[2]); 102 | partitions.put(s[1], info); 103 | } 104 | } catch (Exception e) { 105 | return partitions; 106 | } 107 | 108 | return partitions; 109 | } 110 | 111 | 112 | /** 113 | * Generates a command that remounts the specified partition with 114 | * the desired attributes. 115 | * @param mountPoint The partition to remount. 116 | * @param readOnly Whether to remount the partition in readOnly mode. 117 | * @return The command. 118 | */ 119 | public final String remountPartition( 120 | final String mountPoint, 121 | final boolean readOnly) { 122 | 123 | PartitionInfo info = mPartitions.get(mountPoint); 124 | if (info == null) { 125 | return null; 126 | } 127 | 128 | String mountType; 129 | 130 | if (readOnly) { 131 | mountType = "ro"; 132 | } else { 133 | mountType = "rw"; 134 | } 135 | 136 | String command = "mount -o " + mountType + ",remount -t " 137 | + info.getType() 138 | + " " + info.getDevice() + " " + info.getMountPoint(); 139 | 140 | return command; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/Commands.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.io.OutputStreamWriter; 26 | 27 | /** 28 | * Utility class for issuing system commands. 29 | * @author Vitaly 30 | * 31 | */ 32 | public class Commands { 33 | /** The command to get root access. */ 34 | public static final String SU = "su"; 35 | 36 | /** The PATH environment variable. */ 37 | public static final String PATH = "PATH"; 38 | 39 | /** Runtime. */ 40 | private Runtime mRuntime = Runtime.getRuntime(); 41 | 42 | /** Standard output. */ 43 | private StringBuilder mStdOut = new StringBuilder(); 44 | 45 | /** Standard error output. */ 46 | private StringBuilder mStdErr = new StringBuilder(); 47 | 48 | /** Currently running process. */ 49 | private Process mProc = null; 50 | 51 | /** 52 | * 53 | * @return The standard output. 54 | */ 55 | public final StringBuilder getStdOut() { 56 | return mStdOut; 57 | } 58 | 59 | /** 60 | * 61 | * @return The standard error output. 62 | */ 63 | public final StringBuilder getStdErr() { 64 | return mStdErr; 65 | } 66 | 67 | /** 68 | * @return Whether or not the command to have 69 | * root access is available. 70 | */ 71 | public static boolean checkRoot() { 72 | String paths = System.getenv(PATH); 73 | String[] pathComponents = paths.split(":"); 74 | 75 | for (String path : pathComponents) { 76 | File f = new File(path, SU); 77 | if (f.exists()) { 78 | return true; 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | /** 85 | * Run the given script file as root. 86 | * @param scriptFile The script to run. 87 | */ 88 | public static void runScriptAsRoot(final String scriptFile) { 89 | Commands cmds = new Commands(); 90 | cmds.runCommandAsRoot("sh " + scriptFile); 91 | } 92 | 93 | /** 94 | * Execute the specified command. 95 | * @param command The command to run. 96 | */ 97 | public final void runCommand(final String command) { 98 | try { 99 | mProc = mRuntime.exec(command); 100 | } catch (IOException e) { 101 | // TODO Auto-generated catch block 102 | e.printStackTrace(); 103 | } 104 | } 105 | 106 | /** 107 | * Execute the specified command as root. 108 | * @param command The command to run. 109 | */ 110 | public final void runCommandAsRoot(final String command) { 111 | OutputStreamWriter osw = null; 112 | 113 | try { 114 | mProc = mRuntime.exec("su"); 115 | osw = new OutputStreamWriter(mProc.getOutputStream()); 116 | 117 | osw.write(command); 118 | osw.flush(); 119 | osw.close(); 120 | 121 | } catch (Exception ex) { 122 | ex.printStackTrace(); 123 | } finally { 124 | if (osw != null) { 125 | try { 126 | osw.close(); 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Run the command as root and wait for its termination. 136 | * @param command The command to run. 137 | * @return The status of the command. 138 | */ 139 | public final int runCommandAsRootAndWait(final String command) { 140 | runCommandAsRoot(command); 141 | try { 142 | mProc.waitFor(); 143 | } catch (InterruptedException e) { 144 | e.printStackTrace(); 145 | } 146 | return mProc.exitValue(); 147 | } 148 | 149 | /** 150 | * @return The currently running process. 151 | */ 152 | public final Process getProcess() { 153 | return mProc; 154 | } 155 | 156 | /** 157 | * Checks whether the specified process is currently 158 | * running on the system. 159 | * @param name The name of the process. 160 | * @return Whether the process is running or not. 161 | */ 162 | public static boolean isProgramRunning(final String name) { 163 | Commands cmds = new Commands(); 164 | cmds.runCommandAsRoot("ps"); 165 | 166 | BufferedReader in = new BufferedReader( 167 | new InputStreamReader(cmds.getProcess().getInputStream())); 168 | try { 169 | String l; 170 | while ((l = in.readLine()) != null) { 171 | if (l.contains(name)) { 172 | return true; 173 | } 174 | } 175 | } catch (Exception e) { 176 | return false; 177 | } 178 | 179 | return false; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/net/magictunnel/settings/Settings.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.settings; 20 | 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.HashSet; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Set; 27 | 28 | import android.content.Context; 29 | import android.content.SharedPreferences; 30 | import android.preference.PreferenceManager; 31 | 32 | /** 33 | * This class manages configuration settings for all tunnels. There is only one 34 | * instance of this class in the system. 35 | * 36 | * @author Vitaly 37 | * 38 | */ 39 | public final class Settings { 40 | /** The unique instance of the settings class. */ 41 | private static Settings sSettings = null; 42 | 43 | /** Mapping from profile names to profile objects. */ 44 | private HashMap mProfiles = new HashMap(); 45 | 46 | /** The active profile. */ 47 | private String mCurrentSettingsProfile; 48 | 49 | /** 50 | * This class is not supposed to be instantiated by other clients. 51 | */ 52 | private Settings() { 53 | 54 | } 55 | 56 | /** 57 | * Set the current active profile. 58 | * XXX: Move this elsewhere. 59 | * @param profile the profile name. 60 | */ 61 | public void setCurrentSettingsProfile(final String profile) { 62 | mCurrentSettingsProfile = profile; 63 | } 64 | 65 | /** 66 | * Get the current active profile. 67 | * XXX: Move this elsewhere. 68 | * @return the profile name. 69 | */ 70 | public String getCurrentSettingsProfile() { 71 | return mCurrentSettingsProfile; 72 | } 73 | 74 | 75 | /** 76 | * Fetches the settings of all profiles stored in the 77 | * Android's configuration store. 78 | * @param context The Android context. 79 | * @return A new settings object. 80 | */ 81 | private static Settings retrieveSettings(final Context context) { 82 | Settings s = new Settings(); 83 | SharedPreferences prefs = PreferenceManager 84 | .getDefaultSharedPreferences(context); 85 | Map settings = prefs.getAll(); 86 | Set keys = settings.keySet(); 87 | Set profiles = new HashSet(); 88 | 89 | // Get all profile names from the list of settings 90 | for (String k : keys) { 91 | String[] components = k.split("_"); 92 | if (components.length > 1) { 93 | profiles.add(components[1]); 94 | } 95 | } 96 | 97 | for (String k : profiles) { 98 | Profile p = Profile.retrieveProfile(context, k); 99 | if (p != null) { 100 | s.mProfiles.put(k, p); 101 | } 102 | } 103 | return s; 104 | } 105 | 106 | /** 107 | * Gets the settings for all profiles. 108 | * @param context The Android context. 109 | * @return The unique instance of the settings object. 110 | */ 111 | public static Settings get(final Context context) { 112 | if (sSettings != null) { 113 | return sSettings; 114 | } 115 | 116 | sSettings = retrieveSettings(context); 117 | return sSettings; 118 | } 119 | 120 | /** 121 | * Retrieves the list of all available profile names. 122 | * @return The profile names. 123 | */ 124 | public List getProfileNames() { 125 | ArrayList ret = new ArrayList(mProfiles.keySet()); 126 | return ret; 127 | } 128 | 129 | /** 130 | * Get the profile object that corresponds to the specified 131 | * profile name. 132 | * @param name The profile name to retrieve. 133 | * @return The profile object. 134 | */ 135 | public Profile getProfile(final String name) { 136 | return mProfiles.get(name); 137 | } 138 | 139 | /** 140 | * Renames a profile. 141 | * @param ctx The Android context. 142 | * @param oldName The old profile name. 143 | * @param newName The new profile name. 144 | * @return true if managed to rename the profile successfully. 145 | */ 146 | public boolean rename( 147 | final Context ctx, 148 | final String oldName, 149 | final String newName) { 150 | 151 | Profile profile = mProfiles.get(oldName); 152 | if (profile == null) { 153 | return false; 154 | } 155 | if (oldName.equals(newName)) { 156 | return false; 157 | } 158 | profile.deleteProfile(ctx); 159 | mProfiles.remove(oldName); 160 | mProfiles.put(newName, profile); 161 | profile.setName(newName); 162 | profile.saveProfile(ctx); 163 | return true; 164 | } 165 | 166 | /** 167 | * Adds a new profile to the settings. 168 | * @param profile The profile to add. 169 | */ 170 | public void addProfile(final Profile profile) { 171 | if (mProfiles.containsKey(profile)) { 172 | throw new RuntimeException("Profile already exists"); 173 | } 174 | mProfiles.put(profile.getName(), profile); 175 | } 176 | 177 | /** 178 | * Deletes the specified profile from the settings. 179 | * @param profile The profile to delete. 180 | * @param ctx The Android context. 181 | */ 182 | public void deleteProfile(final Profile profile, final Context ctx) { 183 | profile.deleteProfile(ctx); 184 | mProfiles.remove(profile.getName()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/net/magictunnel/SystemComponentChecklist.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import net.magictunnel.core.Commands; 22 | import net.magictunnel.core.Installer; 23 | import net.magictunnel.core.Tunnel; 24 | import android.content.ActivityNotFoundException; 25 | import android.content.Intent; 26 | import android.net.Uri; 27 | import android.os.Bundle; 28 | import android.preference.CheckBoxPreference; 29 | import android.preference.Preference; 30 | import android.preference.Preference.OnPreferenceClickListener; 31 | import android.preference.PreferenceActivity; 32 | import android.preference.PreferenceCategory; 33 | import android.preference.PreferenceScreen; 34 | import android.widget.Toast; 35 | 36 | /** 37 | * The very first activity the user sees. 38 | * It displays the checklist of installed/missing components 39 | * and proceeds with the installation if all dependencies are met. 40 | * @author Vitaly 41 | * 42 | */ 43 | public class SystemComponentChecklist extends PreferenceActivity { 44 | 45 | /** Whether the user can obtain root access. */ 46 | private boolean mHasRoot = false; 47 | 48 | /** Whether there is a TAP driver installed. */ 49 | private boolean mHasTun = false; 50 | 51 | @Override 52 | protected final void onCreate(final Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | addPreferencesFromResource(R.xml.checklist); 55 | 56 | if (Installer.iodineInstalled()) { 57 | Intent intent = new Intent().setClass(this, TunnelListPreferences.class); 58 | startActivity(intent); 59 | finish(); 60 | } 61 | 62 | populateScreen(); 63 | } 64 | 65 | /** Creates the actual screen. */ 66 | private void populateScreen() { 67 | PreferenceScreen screen = getPreferenceScreen(); 68 | 69 | PreferenceCategory catChecklist = new PreferenceCategory(this); 70 | catChecklist.setTitle(R.string.checklist_category); 71 | 72 | CheckBoxPreference prefRootAccess = createCustomCheckBox(R.string.checklist_root); 73 | mHasRoot = Commands.checkRoot(); 74 | prefRootAccess.setChecked(mHasRoot); 75 | 76 | 77 | CheckBoxPreference prefTun = createCustomCheckBox(R.string.checklist_tun); 78 | mHasTun = Tunnel.checkTap(); 79 | prefTun.setChecked(mHasTun); 80 | 81 | 82 | PreferenceCategory catAction = new PreferenceCategory(this); 83 | catAction.setTitle(R.string.checklist_category_action); 84 | 85 | screen.addPreference(catChecklist); 86 | screen.addPreference(prefRootAccess); 87 | screen.addPreference(prefTun); 88 | screen.addPreference(catAction); 89 | 90 | if (mHasRoot && mHasTun) { 91 | addProceedButton(); 92 | } else { 93 | addHelpButton(); 94 | } 95 | } 96 | 97 | /** 98 | * Custom checkbox. 99 | * @param id The resource id of the title. 100 | * @return the checkbox. 101 | */ 102 | final CheckBoxPreference createCustomCheckBox(final int id) { 103 | CheckBoxPreference checkbox = new CheckBoxPreference(this); 104 | checkbox.setTitle(id); 105 | //checkbox.setWidgetLayoutResource(R.layout.checkbox); 106 | checkbox.setEnabled(false); 107 | 108 | return checkbox; 109 | } 110 | 111 | /** 112 | * Displays install button if all dependencies are satisfied. 113 | */ 114 | private void addProceedButton() { 115 | PreferenceScreen screen = getPreferenceScreen(); 116 | 117 | Preference prefProceed = new Preference(this); 118 | prefProceed.setTitle(R.string.checklist_proceed); 119 | prefProceed.setSummary(R.string.checklist_proceed_subtitle); 120 | 121 | prefProceed.setOnPreferenceClickListener(new OnPreferenceClickListener() { 122 | 123 | @Override 124 | public boolean onPreferenceClick(final Preference preference) { 125 | Intent intent = new Intent().setClass(preference.getContext(), 126 | TunnelListPreferences.class); 127 | 128 | Installer installer = new Installer(preference.getContext()); 129 | 130 | if (!installer.installIodine()) { 131 | Utils.showErrorMessage(preference.getContext(), "Could not install the client"); 132 | return false; 133 | } 134 | 135 | Toast t = Toast.makeText(preference.getContext(), R.string.checklist_install_ok, Toast.LENGTH_LONG); 136 | t.show(); 137 | 138 | preference.getContext().startActivity(intent); 139 | finish(); 140 | return false; 141 | } 142 | }); 143 | 144 | screen.addPreference(prefProceed); 145 | } 146 | 147 | /** 148 | * Display a help button if some dependencies are missing. 149 | */ 150 | private void addHelpButton() { 151 | PreferenceScreen screen = getPreferenceScreen(); 152 | 153 | Preference prefHelp = new Preference(this); 154 | prefHelp.setTitle(R.string.checklist_nok); 155 | prefHelp.setSummary(R.string.checklist_nok_subtitle); 156 | 157 | prefHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() { 158 | 159 | @Override 160 | public boolean onPreferenceClick(final Preference preference) { 161 | String url = preference.getContext().getString(R.string.url_setup_help); 162 | Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(url)); 163 | 164 | try { 165 | preference.getContext().startActivity(intent); 166 | } catch (ActivityNotFoundException ex) { 167 | // do something about the exception, or not ... 168 | return false; 169 | } 170 | 171 | return false; 172 | } 173 | }); 174 | 175 | screen.addPreference(prefHelp); 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/Installer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.File; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.io.OutputStream; 26 | import java.io.PrintWriter; 27 | 28 | import android.content.Context; 29 | import android.content.res.AssetManager; 30 | import android.util.Log; 31 | 32 | /** 33 | * Takes care of copying Iodine files to the system partition. 34 | * @author Vitaly 35 | * 36 | */ 37 | public class Installer { 38 | /** Name of the dynamically-generated installation script. */ 39 | public static final String INSTALL_SCRIPT = "install.sh"; 40 | 41 | /** Where do we copy files to. */ 42 | public static final String TARGET_PARTITION = "/system"; 43 | 44 | /** The iodine executable file in our local data folder. */ 45 | public static final String DNS_TUNNEL_LOCALFILE = "iodine"; 46 | 47 | /** The final location of the iodine file. */ 48 | public static final String DNS_TUNNEL_FILE = "/system/bin/iodine"; 49 | 50 | /** The iodine executable file in our assets. */ 51 | public static final String DNS_TUNNEL_ASSET = "iodine"; 52 | 53 | /** Size of the buffer for copy operations. */ 54 | private static final int BUFFER_SIZE = 512; 55 | 56 | /** 500 ms for timeout during install operation. */ 57 | private static final int TIMEOUT = 500; 58 | 59 | /** Asset manager. */ 60 | private AssetManager mAssets; 61 | 62 | /** Android context. */ 63 | private Context mContext; 64 | 65 | /** 66 | * Initializes the installer class. 67 | * @param context The Android context. 68 | */ 69 | public Installer(final Context context) { 70 | mAssets = context.getAssets(); 71 | mContext = context; 72 | } 73 | 74 | /** 75 | * Copy the file from the input stream to the output stream. 76 | * @param out The destination of the copy. 77 | * @param in The source of the copy. 78 | * @throws IOException in case of problems. 79 | */ 80 | private void copyFile( 81 | final OutputStream out, 82 | final InputStream in) throws IOException { 83 | 84 | byte[] buffer = new byte[BUFFER_SIZE]; 85 | int count = 0; 86 | while ((count = in.read(buffer)) != -1) { 87 | out.write(buffer, 0, count); 88 | } 89 | } 90 | 91 | /** 92 | * Copies the specified asset to its final destination. 93 | * @param sourceAsset the name of the source asset. 94 | * @param destFile the name of the destination file. 95 | * @return the success of the copy. 96 | */ 97 | private boolean installFile( 98 | final String sourceAsset, 99 | final String destFile) { 100 | 101 | InputStream inputStream; 102 | 103 | try { 104 | inputStream = mAssets.open(sourceAsset); 105 | FileOutputStream outputStream = 106 | mContext.openFileOutput(destFile, Context.MODE_PRIVATE); 107 | copyFile(outputStream, inputStream); 108 | outputStream.close(); 109 | inputStream.close(); 110 | return true; 111 | } catch (IOException e) { 112 | Log.e(Installer.class.toString(), e.getMessage()); 113 | return false; 114 | } 115 | } 116 | 117 | /** 118 | * Creates an install script that will run in super user mode. 119 | * @param partition on which to install the DNS tunnel client. 120 | * @param source The source file. 121 | * @param dest The destination path of the iodine client. 122 | * @return the success status of the operation. 123 | */ 124 | public final boolean generateInstallScript( 125 | final String partition, 126 | final String source, 127 | final String dest) { 128 | try { 129 | FileOutputStream fos = 130 | mContext.openFileOutput(INSTALL_SCRIPT, Context.MODE_PRIVATE); 131 | 132 | PrintWriter script = new PrintWriter(fos); 133 | 134 | File privateDir = mContext.getFilesDir(); 135 | File absSource = new File(privateDir, source); 136 | 137 | //Remount read-write 138 | Partition p = new Partition(); 139 | String mountCommand = p.remountPartition(partition, false); 140 | script.println(mountCommand); 141 | 142 | //Copy the file 143 | script.println("cp " + absSource.toString() + " " + dest); 144 | 145 | //Add executable permission 146 | script.println("chmod 700 " + dest); 147 | 148 | //Remount read-only 149 | mountCommand = p.remountPartition(partition, true); 150 | script.println(mountCommand); 151 | 152 | script.close(); 153 | return true; 154 | } catch (Exception e) { 155 | return false; 156 | } 157 | } 158 | 159 | /** 160 | * 161 | * @return Whether the iodine client is installed. 162 | */ 163 | public static boolean iodineInstalled() { 164 | File file = new File(DNS_TUNNEL_FILE); 165 | return file.exists(); 166 | } 167 | 168 | /** 169 | * Performs the installation. 170 | * @return the success status of the operation. 171 | */ 172 | public final boolean installIodine() { 173 | if (iodineInstalled()) { 174 | return true; 175 | } 176 | 177 | //Copy the file to private storage 178 | if (!installFile(DNS_TUNNEL_ASSET, DNS_TUNNEL_LOCALFILE)) { 179 | return false; 180 | } 181 | 182 | //Generate a shell script that will copy the file 183 | //to the root partition 184 | if (!generateInstallScript(TARGET_PARTITION, 185 | DNS_TUNNEL_LOCALFILE, DNS_TUNNEL_FILE)) { 186 | return false; 187 | } 188 | 189 | File script = new File(mContext.getFilesDir(), INSTALL_SCRIPT); 190 | Commands.runScriptAsRoot(script.toString()); 191 | try { 192 | //Give some time for the script to complete. 193 | Thread.sleep(TIMEOUT); 194 | } catch (InterruptedException e) { 195 | // TODO Auto-generated catch block 196 | e.printStackTrace(); 197 | } 198 | return iodineInstalled(); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/RouteEntry.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.net.InetAddress; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * This class models a routing table entry. 27 | * @author Vitaly 28 | * 29 | */ 30 | public class RouteEntry { 31 | 32 | /** 33 | * Regular expression that represents an address of the form: 34 | * 192.168.1.2/24, at a line start. 35 | */ 36 | private static final Pattern REGEX_DESTINATION = 37 | Pattern.compile("^(\\d+\\.\\d+\\.\\d+\\.\\d+)(?:/(\\d+))?"); 38 | 39 | /** 40 | * Regular expression that represents a gateway entry of the form: 41 | * via 192.168.1.2. 42 | */ 43 | private static final Pattern REGEX_GW = 44 | Pattern.compile("via\\s+(\\d+\\.\\d+.\\d+.\\d+)"); 45 | 46 | /** 47 | * Regex of the form: 48 | * dev eth0. 49 | */ 50 | private static final Pattern REGEX_DEV = 51 | Pattern.compile("dev\\s+(\\w+)"); 52 | 53 | /** 54 | * The maximum length of a network mask. 55 | */ 56 | private static final int MAX_MASK_LENGTH = 32; 57 | 58 | /** 59 | * The destination network. 60 | */ 61 | private int destination; 62 | 63 | /** 64 | * The gateway through which the destination 65 | * can be reached. 66 | */ 67 | private int gateway; 68 | 69 | /** 70 | * The subnet mask. 71 | */ 72 | private int mask; 73 | 74 | /** 75 | * The name of the interface. 76 | */ 77 | private String iface; 78 | 79 | 80 | /** 81 | * Returns the 32-bit address of the destination network. 82 | * @return The destination network. 83 | */ 84 | public final int getDestination() { 85 | return destination; 86 | } 87 | 88 | /** 89 | * Sets the 32-bit address of the destination network. 90 | * @param destinationNetwork The destination network. 91 | */ 92 | public final void setDestination(final int destinationNetwork) { 93 | this.destination = destinationNetwork; 94 | } 95 | 96 | /** 97 | * Returns the 32-bit address of the gateway. 98 | * @return The gateway address. 99 | */ 100 | public final int getGateway() { 101 | return gateway; 102 | } 103 | 104 | /** 105 | * Sets the 32-bit address of the gateway. 106 | * @param gatewayAddress The gateway address. 107 | */ 108 | public final void setGateway(final int gatewayAddress) { 109 | this.gateway = gatewayAddress; 110 | } 111 | 112 | /** 113 | * Gets the 32-bit network mask. 114 | * @return The network mask. 115 | */ 116 | public final int getMask() { 117 | return mask; 118 | } 119 | 120 | /** 121 | * Sets the 32-bit network mask. 122 | * @param networkMask The network mask. 123 | */ 124 | public final void setMask(final int networkMask) { 125 | this.mask = networkMask; 126 | } 127 | 128 | 129 | /** 130 | * Retrieves the name of the network interface. 131 | * For example, /dev/eth0. 132 | * @return The interface name. 133 | */ 134 | public final String getInterfaceName() { 135 | return iface; 136 | } 137 | 138 | 139 | /** 140 | * Sets the name of the network interface. 141 | * @param interfaceName The interface name. 142 | */ 143 | public final void setInterfaceName(final String interfaceName) { 144 | this.iface = interfaceName; 145 | } 146 | 147 | @Override 148 | public final String toString() { 149 | InetAddress strDest = NetworkUtils.intToInetAddress(destination); 150 | InetAddress strGw = NetworkUtils.intToInetAddress(gateway); 151 | InetAddress strMask = NetworkUtils.intToInetAddress(mask); 152 | 153 | return strDest + " " + strMask + " via " + strGw + " dev " + iface; 154 | } 155 | 156 | 157 | /** 158 | * Parses the following types of entries: 159 | * 172.20.2.39 via 10.50.242.169 dev rmnet0 160 | * 10.50.242.168/29 dev rmnet0 proto kernel scope link src 10.50.242.171 161 | * 192.168.233.0/24 dev dns0 proto kernel scope link src 192.168.233.2 162 | * default dev dns0 scope link 163 | * 164 | * This is equivalent to the following netstat -nrt 165 | * Destination Gateway Genmask Flags MSS Window irtt Iface 166 | * 172.20.2.39 10.50.242.169 255.255.255.255 UGH 0 0 0 rmnet0 167 | * 10.50.242.168 0.0.0.0 255.255.255.248 U 0 0 0 rmnet0 168 | * 192.168.233.0 0.0.0.0 255.255.255.0 U 0 0 0 dns0 169 | * 0.0.0.0 0.0.0.0 0.0.0.0 U 0 0 0 dns0 170 | * 171 | * However, we don't use netstat -nrt because its output is not 172 | * the same on all devices. 173 | * @param cmdOutput Stripped line output from ip route command 174 | * @return A RouteEntry object that represents the output of the 175 | * ip route command. 176 | */ 177 | public static RouteEntry fromIpRouteCommand(final String cmdOutput) { 178 | RouteEntry re = new RouteEntry(); 179 | 180 | //Parse the destination, which is the column 181 | if (cmdOutput.startsWith("default")) { 182 | re.destination = 0; 183 | re.gateway = 0; 184 | } else { 185 | Matcher m = REGEX_DESTINATION.matcher(cmdOutput); 186 | 187 | if (m.find()) { 188 | re.destination = NetworkUtils.v4StringToInt(m.group(1)); 189 | if (m.group(2) != null) { 190 | re.mask = NetworkUtils.prefixLengthToMask( 191 | Integer.valueOf(m.group(2)) 192 | ); 193 | } else { 194 | re.mask = MAX_MASK_LENGTH; 195 | } 196 | } else { 197 | return null; 198 | } 199 | } 200 | 201 | //Parse the gateway 202 | Matcher m = REGEX_GW.matcher(cmdOutput); 203 | if (m.find()) { 204 | re.gateway = NetworkUtils.v4StringToInt(m.group(1)); 205 | } 206 | 207 | //Parse the device 208 | m = REGEX_DEV.matcher(cmdOutput); 209 | if (m.find()) { 210 | re.iface = m.group(1); 211 | } 212 | 213 | return re; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello World, MagicTunnel! 4 | MagicTunnel 5 | MagicTunnel 6 | MagicTunnel 7 | MagicTunnel Log 8 | System component check 9 | Help 10 | MagicTunnel Help 11 | About 12 | Delete 13 | Change settings 14 | Connect 15 | Disconnect 16 | Autodetect 17 | 18 | Tunnel management 19 | Add new tunnel 20 | Available tunnels 21 | Connected 22 | 23 | 24 | Autodetect 25 | Yes 26 | No 27 | 28 | 29 | 30 | AUTODETECT 31 | YES 32 | NO 33 | 34 | 35 | 36 | 37 | Autodetect 38 | NULL 39 | TXT 40 | SRV 41 | MX 42 | CNAME 43 | A 44 | 45 | 46 | 47 | AUTODETECT 48 | NULL 49 | TXT 50 | SRV 51 | MX 52 | CNAME 53 | A 54 | 55 | 56 | 57 | Cellular 58 | Wifi 59 | 60 | 61 | 62 | CELLULAR 63 | WIFI 64 | 65 | 66 | Create new profile 67 | Enter profile name 68 | Profile name 69 | 70 | DNS tunnel settings 71 | Network interface 72 | Tunnel through this interface 73 | 74 | Domain name 75 | Tunnel endpoint 76 | 77 | Password 78 | 79 | Packet size 80 | Use direct connection to DNS tunnel server 81 | Use direct connection 82 | Protocol type 83 | 84 | 85 | Yes 86 | No 87 | OK 88 | Cancel 89 | Save 90 | 91 | 92 | Password not set 93 | Password not changed 94 | Password set 95 | 96 | The chosen profile name already exists 97 | Please select the interface to use for the tunnel 98 | Please enter the domain name of the tunnel endpoint 99 | Please name the profile 100 | 101 | Are you sure you want to cancel profile creation? 102 | Are you sure you want to discard changes? 103 | Are you sure you want to delete the profile? 104 | 105 | 106 | Installed components 107 | Root access 108 | TUN/TAP driver 109 | 110 | 111 | Install MagicTunnel 112 | Copy tunnel daemon to system partition 113 | Required components missing 114 | Tap here for help 115 | Successfully installed 116 | 117 | 118 | Invalid interface specified 119 | 120 | 121 | Cannot establish tunnel 122 | No route to reach the Internet 123 | Try to disable and reenable your network connection 124 | Could not reroute traffic through tunnel. Try to disable and reenable your network connection. 125 | It is possible that the access point does not allow external DNS traffic. 126 | Check the log for more information. 127 | Please activate the mobile connection 128 | Please activate the Wifi connection 129 | Please activate the mobile or Wifi connection 130 | DNS tunnel established 131 | DNS tunnel disconnected 132 | 133 | 134 | Connection Log 135 | Donate 136 | Online Help 137 | About... 138 | 139 | 140 | This page shows the messages from the last connection attempt. 141 | You can use them to diagnose connectivity problems. 142 | The log is empty. Connect to a tunnel first. 143 | Copy log to clipboard 144 | 145 | 146 | http://www.magictunnel.net/ 147 | http://www.magictunnel.net/ 148 | http://www.magictunnel.net/install.php 149 | http://www.magictunnel.net/donate.php 150 | 151 | 152 | MagicTunnel uses a customized iodine DNS tunneling client. 153 | Source code, licenses, and documentation available online. 154 | http://www.magictunnel.net 155 | 156 | -------------------------------------------------------------------------------- /src/net/magictunnel/old/TunnelListActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.old; 20 | 21 | import java.util.ArrayList; 22 | import java.util.TreeSet; 23 | 24 | import net.magictunnel.R; 25 | import net.magictunnel.R.id; 26 | import net.magictunnel.R.layout; 27 | import net.magictunnel.R.menu; 28 | 29 | import android.view.ContextMenu; 30 | import android.view.ContextMenu.ContextMenuInfo; 31 | import android.view.LayoutInflater; 32 | import android.view.MenuInflater; 33 | import android.view.MenuItem; 34 | import android.view.View; 35 | import android.view.ViewGroup; 36 | import android.app.Activity; 37 | import android.app.ListActivity; 38 | import android.content.Context; 39 | import android.os.Bundle; 40 | import android.widget.AdapterView; 41 | import android.widget.AdapterView.AdapterContextMenuInfo; 42 | import android.widget.AdapterView.OnItemClickListener; 43 | import android.widget.ArrayAdapter; 44 | import android.widget.BaseAdapter; 45 | import android.widget.ListView; 46 | import android.widget.TextView; 47 | import android.widget.Toast; 48 | 49 | public class TunnelListActivity extends ListActivity { 50 | private TunnelListAdapter mAdapter; 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | mAdapter = new TunnelListAdapter(); 56 | 57 | mAdapter.addItem("Setup New DNS Tunnel", TunnelListAdapter.TYPE_CREATE_NEW); 58 | mAdapter.addItem("Available Tunnels", TunnelListAdapter.TYPE_SEPARATOR); 59 | 60 | for (int i = 1; i < 5; i++) { 61 | mAdapter.addItem("Tunnel " + i, TunnelListAdapter.TYPE_SETTING); 62 | } 63 | setListAdapter(mAdapter); 64 | } 65 | 66 | 67 | 68 | @Override 69 | public void onCreateContextMenu(ContextMenu menu, View v, 70 | ContextMenuInfo menuInfo) { 71 | super.onCreateContextMenu(menu, v, menuInfo); 72 | MenuInflater inflater = getMenuInflater(); 73 | inflater.inflate(R.menu.tunnellistpref_context, menu); 74 | TextView tv = (TextView)v; 75 | menu.setHeaderTitle(tv.getText()); 76 | } 77 | 78 | @Override 79 | public boolean onContextItemSelected(MenuItem item) { 80 | AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); 81 | switch (item.getItemId()) { 82 | case R.id.cfg_menu_delete: 83 | return true; 84 | case R.id.cfg_menu_change: 85 | return true; 86 | default: 87 | return super.onContextItemSelected(item); 88 | } 89 | } 90 | 91 | /** 92 | * Inspired from http://android.amberfog.com/?p=296 93 | * @author vitaly 94 | * 95 | */ 96 | private class TunnelListAdapter extends BaseAdapter { 97 | 98 | public static final int TYPE_SETTING = 0; 99 | public static final int TYPE_SEPARATOR = 1; 100 | public static final int TYPE_CREATE_NEW = 2; 101 | public static final int TYPE_MAX_COUNT = TYPE_CREATE_NEW + 1; 102 | 103 | private LayoutInflater m_inflater; 104 | private ArrayList m_data = new ArrayList(); 105 | 106 | public TunnelListAdapter() { 107 | m_inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); 108 | } 109 | 110 | public void addItem(String label, int type) { 111 | SettingsItem item = new SettingsItem(label, type); 112 | m_data.add(item); 113 | notifyDataSetChanged(); 114 | } 115 | 116 | @Override 117 | public int getItemViewType(int position) { 118 | return m_data.get(position).m_type; 119 | } 120 | 121 | @Override 122 | public int getViewTypeCount() { 123 | return TYPE_MAX_COUNT; 124 | } 125 | 126 | @Override 127 | public int getCount() { 128 | return m_data.size(); 129 | } 130 | 131 | @Override 132 | public SettingsItem getItem(int position) { 133 | return m_data.get(position); 134 | } 135 | 136 | @Override 137 | public long getItemId(int position) { 138 | return position; 139 | } 140 | 141 | @Override 142 | public boolean isEnabled(int position) { 143 | int type = getItemViewType(position); 144 | return (type != TYPE_SEPARATOR); 145 | } 146 | 147 | @Override 148 | public View getView(int position, View convertView, ViewGroup parent) { 149 | ViewHolder holder = null; 150 | int type = getItemViewType(position); 151 | System.out.println("getView " + position + " " + convertView + " type = " + type); 152 | if (convertView == null) { 153 | holder = new ViewHolder(); 154 | 155 | switch (type) { 156 | case TYPE_CREATE_NEW: 157 | convertView = m_inflater.inflate(R.layout.tunnelslistitem, null); 158 | holder.textView = (TextView)convertView; 159 | break; 160 | 161 | case TYPE_SETTING: 162 | convertView = m_inflater.inflate(R.layout.tunnelslistitem, null); 163 | holder.textView = (TextView)convertView; 164 | registerForContextMenu(convertView); 165 | break; 166 | 167 | case TYPE_SEPARATOR: 168 | convertView = m_inflater.inflate(R.layout.tunnelslistitemseparator, null); 169 | holder.textView = (TextView)convertView; 170 | break; 171 | } 172 | convertView.setTag(holder); 173 | } else { 174 | holder = (ViewHolder)convertView.getTag(); 175 | } 176 | holder.textView.setText(m_data.get(position).getLabel()); 177 | 178 | return convertView; 179 | } 180 | 181 | } 182 | 183 | public static class ViewHolder { 184 | public TextView textView; 185 | } 186 | 187 | public static class SettingsItem implements Comparable { 188 | private String m_label; 189 | private Integer m_type; 190 | 191 | public String getLabel() { 192 | return m_label; 193 | } 194 | 195 | public void setLabel(String label) { 196 | this.m_label = label; 197 | } 198 | 199 | public Integer getType() { 200 | return m_type; 201 | } 202 | 203 | public void setType(Integer type) { 204 | this.m_type = type; 205 | } 206 | 207 | public SettingsItem(String label, int type) { 208 | m_label = label; 209 | m_type = type; 210 | } 211 | 212 | @Override 213 | public int compareTo(SettingsItem another) { 214 | if (m_type == another.m_type) { 215 | return m_label.compareTo(another.m_label); 216 | } 217 | return m_type.compareTo(another.m_type); 218 | } 219 | } 220 | 221 | } 222 | 223 | -------------------------------------------------------------------------------- /src/net/magictunnel/settings/Profile.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.settings; 20 | 21 | import android.content.Context; 22 | import android.content.SharedPreferences; 23 | import android.content.SharedPreferences.Editor; 24 | import android.preference.PreferenceManager; 25 | 26 | /** 27 | * Gathers all profile settings and provides 28 | * mechanisms to store these settings in the Android's 29 | * configuration store. 30 | * 31 | * Profile configuration entries are of the form: 32 | * profile_name_configuration 33 | * 34 | * For example: profile_mytunnel_domain. 35 | * @author Vitaly 36 | * 37 | */ 38 | public class Profile implements Comparable { 39 | /** Prefix for every profile entry. */ 40 | public static final String PROFILE_PREFIX = "profile_"; 41 | 42 | /** Type configuration suffix. */ 43 | public static final String PROFILE_TYPE = "_type"; 44 | 45 | /** Domain configuration suffix. */ 46 | public static final String PROFILE_DOMAIN = "_domain"; 47 | 48 | /** Password configuration suffix. */ 49 | public static final String PROFILE_PASSWORD = "_password"; 50 | 51 | /** Packet size configuration suffix. */ 52 | public static final String PROFILE_MAX_PACKET_SIZE = "_packet_size"; 53 | 54 | /** Packet size configuration suffix. */ 55 | public static final String PROFILE_DO_RAW_DETECTION = "_raw_detection"; 56 | 57 | /** Tunnel encoding type configuration suffix. */ 58 | public static final String PROFILE_ENCODING_TYPE = "_encoding"; 59 | 60 | /** The value of PROFILE_TYPE for DNS tunneling. */ 61 | public static final String PROFILE_TYPE_DNSTUNNEL = "dnstunnel"; 62 | 63 | /** Profile name. */ 64 | private String mName; 65 | 66 | /** Profile type (e.g., PROFILE_TYPE_DNSTUNNEL). */ 67 | private String mType = PROFILE_TYPE_DNSTUNNEL; 68 | 69 | /** Domain name of the tunnel. */ 70 | private String mDomainName; 71 | 72 | /** Password to access the tunnel. */ 73 | private String mPassword; 74 | 75 | /** 76 | * Packet size to use when transmitting queries. 77 | * Zero for auto-detection. 78 | */ 79 | private int mPacketSize = 0; 80 | 81 | /** 82 | * Speed up connection by skipping the detection 83 | * of whether it is possible to connect to the DNS 84 | * server directly. Since most of the time it is not 85 | * possible, this option speeds up connection. 86 | */ 87 | private DnsRawConnection mRawConnection = DnsRawConnection.AUTODETECT; 88 | 89 | /** 90 | * Specifies which protocol to use for tunneling data 91 | * over the DNS tunnel. 92 | */ 93 | private DnsProtocol mDnsProtocol = DnsProtocol.AUTODETECT; 94 | 95 | /** 96 | * Creates a default DNS tunneling profile. 97 | */ 98 | public Profile() { 99 | 100 | } 101 | 102 | /** 103 | * Get the name of the profile. 104 | * @return The profile name. 105 | */ 106 | public final String getName() { 107 | return mName; 108 | } 109 | 110 | /** 111 | * Set the name of the profile. 112 | * @param name The profile name. 113 | */ 114 | public final void setName(final String name) { 115 | mName = name; 116 | } 117 | 118 | /** 119 | * Get the domain name of the tunnel. 120 | * @return The domain name. 121 | */ 122 | public final String getDomainName() { 123 | return mDomainName; 124 | } 125 | 126 | /** 127 | * Set the tunnel's domain name. 128 | * @param name The domain name. 129 | */ 130 | public final void setDomainName(final String name) { 131 | mDomainName = name; 132 | } 133 | 134 | /** 135 | * Get the tunnel's password. 136 | * @return The password. 137 | */ 138 | public final String getPassword() { 139 | return mPassword; 140 | } 141 | 142 | /** 143 | * Set the tunnel password. 144 | * @param password The password. 145 | */ 146 | public final void setPassword(final String password) { 147 | mPassword = password; 148 | } 149 | 150 | /** 151 | * Set the maximum size of DNS packets. 152 | * @param packetSize The packet size 153 | */ 154 | public final void setPacketSize(final int packetSize) { 155 | mPacketSize = packetSize; 156 | 157 | if (mPacketSize < 0) { 158 | mPacketSize = 0; 159 | } 160 | } 161 | 162 | /** 163 | * Get the current maximum packet size. 164 | * @return The packet size. 165 | */ 166 | public final int getPacketSize() { 167 | return mPacketSize; 168 | } 169 | 170 | /** 171 | * Set raw connection detection. 172 | * @param b Whether to perform detection or not. 173 | */ 174 | public final void setRawConnection(final DnsRawConnection b) { 175 | mRawConnection = b; 176 | } 177 | 178 | /** 179 | * 180 | * @return Whether to perform raw detection or not. 181 | */ 182 | public final DnsRawConnection getRawConnection() { 183 | return mRawConnection; 184 | } 185 | 186 | /** 187 | * 188 | * @return Which protocol is currently used for encoding. 189 | */ 190 | public final DnsProtocol getDnsProtocol() { 191 | return mDnsProtocol; 192 | } 193 | 194 | /** 195 | * Set the tunnel protocol. 196 | * @param protocol The protocol. 197 | */ 198 | public final void setDnsProtocl(final DnsProtocol protocol) { 199 | mDnsProtocol = protocol; 200 | } 201 | 202 | @Override 203 | public final int compareTo(final Profile another) { 204 | return mName.compareTo(another.mName); 205 | } 206 | 207 | /** 208 | * Creates a Profile object from the data stored in 209 | * the Android's configuration store. 210 | * @param context The Android context where the configuration is stored. 211 | * @param name The name of the configuration entry. 212 | * @return The associated profile object. 213 | */ 214 | public static Profile retrieveProfile( 215 | final Context context, 216 | final String name) { 217 | 218 | Profile prof = new Profile(); 219 | prof.mName = name; 220 | 221 | String prefixedName = PROFILE_PREFIX + name; 222 | 223 | SharedPreferences prefs = 224 | PreferenceManager.getDefaultSharedPreferences(context); 225 | 226 | if (!prefs.contains(prefixedName + PROFILE_TYPE)) { 227 | return null; 228 | } 229 | 230 | prof.mType = prefs.getString(prefixedName + PROFILE_TYPE, "dnstunnel"); 231 | if (!prof.mType.equals("dnstunnel")) { 232 | return null; 233 | } 234 | 235 | 236 | prof.mDomainName = prefs.getString(prefixedName + PROFILE_DOMAIN, ""); 237 | prof.mPassword = prefs.getString(prefixedName + PROFILE_PASSWORD, ""); 238 | prof.mPacketSize = prefs.getInt(prefixedName + PROFILE_MAX_PACKET_SIZE, 0); 239 | prof.mRawConnection = DnsRawConnection.valueOf(prefs.getString( 240 | prefixedName + PROFILE_DO_RAW_DETECTION, 241 | DnsRawConnection.AUTODETECT.toString())); 242 | 243 | prof.mDnsProtocol = DnsProtocol.valueOf(prefs.getString( 244 | prefixedName + PROFILE_ENCODING_TYPE, 245 | DnsProtocol.AUTODETECT.toString())); 246 | return prof; 247 | } 248 | 249 | /** 250 | * Stores this profile into the Android's configuration store. 251 | * @param context The Android context. 252 | */ 253 | public final void saveProfile(final Context context) { 254 | String prefixedName = PROFILE_PREFIX + mName; 255 | SharedPreferences prefs = 256 | PreferenceManager.getDefaultSharedPreferences(context); 257 | 258 | Editor edit = prefs.edit(); 259 | edit.putString(prefixedName + PROFILE_TYPE, mType); 260 | edit.putString(prefixedName + PROFILE_DOMAIN, mDomainName); 261 | edit.putString(prefixedName + PROFILE_PASSWORD, mPassword); 262 | edit.putString(prefixedName + PROFILE_ENCODING_TYPE, mDnsProtocol.toString()); 263 | edit.putInt(prefixedName + PROFILE_MAX_PACKET_SIZE, mPacketSize); 264 | edit.putString(prefixedName + PROFILE_DO_RAW_DETECTION, mRawConnection.toString()); 265 | edit.commit(); 266 | } 267 | 268 | /** 269 | * Removes all profile entries from the Android's configuration store. 270 | * @param context The Android context. 271 | */ 272 | public final void deleteProfile(final Context context) { 273 | String prefixedName = PROFILE_PREFIX + mName; 274 | SharedPreferences prefs = 275 | PreferenceManager.getDefaultSharedPreferences(context); 276 | 277 | Editor edit = prefs.edit(); 278 | edit.remove(prefixedName + PROFILE_TYPE); 279 | edit.remove(prefixedName + PROFILE_DOMAIN); 280 | edit.remove(prefixedName + PROFILE_PASSWORD); 281 | edit.remove(prefixedName + PROFILE_ENCODING_TYPE); 282 | edit.remove(prefixedName + PROFILE_MAX_PACKET_SIZE); 283 | edit.remove(prefixedName + PROFILE_DO_RAW_DETECTION); 284 | edit.commit(); 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/net/magictunnel/TunnelListPreferences.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import java.util.List; 22 | 23 | import net.magictunnel.core.ITunnelStatusListener; 24 | import net.magictunnel.core.Iodine; 25 | import net.magictunnel.settings.Profile; 26 | import net.magictunnel.settings.Settings; 27 | import android.app.AlertDialog; 28 | import android.app.Dialog; 29 | import android.content.DialogInterface; 30 | import android.content.Intent; 31 | import android.os.Bundle; 32 | import android.preference.Preference; 33 | import android.preference.Preference.OnPreferenceClickListener; 34 | import android.preference.PreferenceActivity; 35 | import android.preference.PreferenceCategory; 36 | import android.preference.PreferenceScreen; 37 | import android.view.ContextMenu; 38 | import android.view.Menu; 39 | import android.view.MenuInflater; 40 | import android.view.MenuItem; 41 | import android.view.View; 42 | import android.view.ContextMenu.ContextMenuInfo; 43 | import android.widget.AdapterView.AdapterContextMenuInfo; 44 | 45 | /** 46 | * List of available tunnels with options to create new tunnels. 47 | * @author Vitaly 48 | * 49 | */ 50 | public class TunnelListPreferences extends PreferenceActivity implements 51 | ITunnelStatusListener { 52 | 53 | /** Log menu ID. */ 54 | private static final int MENU_LOG = Menu.FIRST; 55 | 56 | /** Donate menu ID. */ 57 | //private static final int MENU_DONATE = Menu.FIRST + 1; 58 | 59 | /** About menu ID. */ 60 | private static final int MENU_ABOUT = Menu.FIRST + 2; 61 | 62 | /** Delete profile confirmation id. */ 63 | private static final int CONFIRM_DELETE_DIALOG_ID = 0; 64 | 65 | /** The index of the first tunnel in the list of items. */ 66 | private int mFirstTunnelIndex = 0; 67 | 68 | /** Profile name to delete. */ 69 | private String mProfileToDelete; 70 | 71 | @Override 72 | protected final void onCreate(final Bundle savedInstanceState) { 73 | super.onCreate(savedInstanceState); 74 | addPreferencesFromResource(R.xml.tunnellist); 75 | registerForContextMenu(getListView()); 76 | 77 | MagicTunnel mt = ((MagicTunnel) getApplication()); 78 | Iodine iod = mt.getIodine(); 79 | iod.registerListener(this); 80 | 81 | populateScreen(); 82 | } 83 | 84 | @Override 85 | protected final void onDestroy() { 86 | MagicTunnel mt = ((MagicTunnel) getApplication()); 87 | Iodine iod = mt.getIodine(); 88 | iod.unregisterListener(this); 89 | super.onDestroy(); 90 | } 91 | 92 | /** 93 | * Fills in the screen with items. 94 | * The upper part of the screen is a special entry 95 | * that opens an activity for adding a new tunnel. 96 | */ 97 | private void populateScreen() { 98 | PreferenceScreen screen = getPreferenceScreen(); 99 | screen.removeAll(); 100 | 101 | screen.addPreference(createCategory(R.string.tunnel_mgmt)); 102 | 103 | Preference pref = createAddNewTunnel(); 104 | screen.addPreference(pref); 105 | 106 | pref = createCategory(R.string.available_tunnels); 107 | screen.addPreference(pref); 108 | mFirstTunnelIndex = pref.getOrder() + 1; 109 | } 110 | 111 | /** 112 | * Show the tunnel preferences activity. 113 | * @param name The tunnel that we want to edit. 114 | */ 115 | private void showTunnelPreferences(final String name) { 116 | MagicTunnel app = (MagicTunnel) getApplication(); 117 | Settings s = app.getSettings(); 118 | s.setCurrentSettingsProfile(name); 119 | 120 | Intent intent = new Intent().setClass(TunnelListPreferences.this, 121 | TunnelPreferences.class); 122 | startActivity(intent); 123 | } 124 | 125 | /** 126 | * Creates a new preference category. 127 | * @param titleId The resource id of the title. 128 | * @return The preference category. 129 | */ 130 | private PreferenceCategory createCategory(final int titleId) { 131 | PreferenceCategory pref = new PreferenceCategory(this); 132 | pref.setTitle(titleId); 133 | return pref; 134 | } 135 | 136 | /** 137 | * Creates a list entry for adding new tunnels. 138 | * @return The preference entry. 139 | */ 140 | private Preference createAddNewTunnel() { 141 | Preference pref = new Preference(this); 142 | pref.setTitle(R.string.add_new_tunnel); 143 | 144 | pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 145 | public boolean onPreferenceClick(final Preference preference) { 146 | showTunnelPreferences(""); 147 | return true; 148 | } 149 | 150 | }); 151 | return pref; 152 | } 153 | 154 | /** 155 | * Shows a menu when the user long-pressed a tunnel entry. 156 | * @param menu The menu 157 | * @param v The view 158 | * @param menuInfo The menu info. 159 | */ 160 | @Override 161 | public final void onCreateContextMenu( 162 | final ContextMenu menu, 163 | final View v, 164 | final ContextMenuInfo menuInfo) { 165 | 166 | super.onCreateContextMenu(menu, v, menuInfo); 167 | 168 | String profile = getSelectedProfile((AdapterContextMenuInfo) menuInfo); 169 | if (profile == null) { 170 | return; 171 | } 172 | 173 | MenuInflater inflater = getMenuInflater(); 174 | 175 | if (getConnectedProfile().equals(profile)) { 176 | inflater.inflate(R.menu.tunnellistpref_disconnect, menu); 177 | } else { 178 | inflater.inflate(R.menu.tunnellistpref_context, menu); 179 | } 180 | menu.setHeaderTitle(profile); 181 | } 182 | 183 | /** 184 | * Determines the name of the selected profile based on the 185 | * given menu information. 186 | * @param menuInfo The menu information. 187 | * @return The name of the selected profile, null if no profile selected. 188 | */ 189 | private String getSelectedProfile(final AdapterContextMenuInfo menuInfo) { 190 | // excludes mVpnListContainer and the preferences above it 191 | int position = menuInfo.position - mFirstTunnelIndex; 192 | if (position < 0) { 193 | return null; 194 | } 195 | PreferenceScreen prefs = getPreferenceScreen(); 196 | Preference pref = prefs.getPreference(menuInfo.position); 197 | return pref.getTitle().toString(); 198 | } 199 | 200 | /** Disconnects iodine. */ 201 | private void doDisconnect() { 202 | MagicTunnel mt = ((MagicTunnel) getApplication()); 203 | Iodine iod = mt.getIodine(); 204 | iod.disconnect(); 205 | } 206 | 207 | /** 208 | * Connects the tunnel. 209 | * @param profileName The profile name where to take the settings from. 210 | */ 211 | private void doConnect(final String profileName) { 212 | MagicTunnel mt = ((MagicTunnel) getApplication()); 213 | Settings s = mt.getSettings(); 214 | Profile p = s.getProfile(profileName); 215 | 216 | if (p == null) { 217 | return; 218 | } 219 | 220 | Iodine iod = mt.getIodine(); 221 | if (!getConnectedProfile().equals("")) { 222 | iod.disconnect(); 223 | } 224 | iod.setContext(this); 225 | iod.getLauncher(p).execute((Void) null); 226 | } 227 | 228 | @Override 229 | public final boolean onContextItemSelected(final MenuItem item) { 230 | AdapterContextMenuInfo info = (AdapterContextMenuInfo) item 231 | .getMenuInfo(); 232 | 233 | PreferenceScreen prefs = getPreferenceScreen(); 234 | Preference pref = prefs.getPreference(info.position); 235 | String profileName = pref.getTitle().toString(); 236 | 237 | switch (item.getItemId()) { 238 | 239 | case R.id.cfg_menu_disconnect: 240 | doDisconnect(); 241 | return true; 242 | 243 | case R.id.cfg_menu_connect: 244 | doConnect(profileName); 245 | return true; 246 | 247 | case R.id.cfg_menu_delete: 248 | mProfileToDelete = profileName; 249 | showDialog(CONFIRM_DELETE_DIALOG_ID); 250 | return true; 251 | 252 | case R.id.cfg_menu_change: 253 | showTunnelPreferences(profileName); 254 | return true; 255 | 256 | default: 257 | return super.onContextItemSelected(item); 258 | } 259 | } 260 | 261 | @Override 262 | protected final void onResume() { 263 | super.onResume(); 264 | populateTunnels(); 265 | } 266 | 267 | /** 268 | * 269 | * @return The name of the currently connected profile. 270 | */ 271 | private String getConnectedProfile() { 272 | MagicTunnel app = (MagicTunnel) getApplication(); 273 | Iodine iod = app.getIodine(); 274 | 275 | boolean isConnected = iod.isIodineRunning(); 276 | Profile activeProfile = iod.getActiveProfile(); 277 | 278 | if (!isConnected || activeProfile == null) { 279 | return ""; 280 | } 281 | return activeProfile.getName(); 282 | } 283 | 284 | /** 285 | * Adds all the existing tunnels to the list of 286 | * available tunnels. 287 | */ 288 | private void populateTunnels() { 289 | Preference pref; 290 | PreferenceScreen screen = getPreferenceScreen(); 291 | MagicTunnel app = (MagicTunnel) getApplication(); 292 | String activeProfile = getConnectedProfile(); 293 | 294 | // Remove old tunnels from the list 295 | while (screen.getPreferenceCount() > mFirstTunnelIndex) { 296 | screen.removePreference(screen.getPreference(mFirstTunnelIndex)); 297 | } 298 | 299 | Settings s = app.getSettings(); 300 | List profiles = s.getProfileNames(); 301 | 302 | int position = mFirstTunnelIndex; 303 | for (String p : profiles) { 304 | pref = new Preference(this); 305 | pref.setTitle(p); 306 | 307 | if (activeProfile.equals(p)) { 308 | pref.setSummary(R.string.connected_tunnel); 309 | } else { 310 | pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 311 | @Override 312 | public boolean onPreferenceClick(final Preference preference) { 313 | doConnect(preference.getTitle().toString()); 314 | return true; 315 | } 316 | }); 317 | } 318 | 319 | pref.setOrder(position); 320 | screen.addPreference(pref); 321 | ++position; 322 | } 323 | 324 | } 325 | 326 | /** 327 | * Deletes the specified profile, then refreshes 328 | * the list of existing profiles. 329 | * @param profileName The profile to delete. 330 | */ 331 | private void doDeleteProfile(final String profileName) { 332 | MagicTunnel app = (MagicTunnel) getApplication(); 333 | Settings s = app.getSettings(); 334 | Profile profile = s.getProfile(profileName); 335 | s.deleteProfile(profile, TunnelListPreferences.this); 336 | populateTunnels(); 337 | } 338 | 339 | /** 340 | * Confirm the profile deletion. 341 | * @param id The dialog id. 342 | * @return The create confirm dialog. 343 | */ 344 | @Override 345 | protected final Dialog onCreateDialog(final int id) { 346 | 347 | if (id == CONFIRM_DELETE_DIALOG_ID) { 348 | return new AlertDialog.Builder(this) 349 | .setTitle(android.R.string.dialog_alert_title) 350 | .setIcon(android.R.drawable.ic_dialog_alert) 351 | .setMessage(R.string.confirm_profile_deletion) 352 | .setPositiveButton( 353 | R.string.yes, 354 | new DialogInterface.OnClickListener() { 355 | public void onClick(final DialogInterface dialog, final int w) { 356 | doDeleteProfile(mProfileToDelete); 357 | mProfileToDelete = null; 358 | } 359 | }) 360 | .setNegativeButton(R.string.no, null).create(); 361 | } 362 | 363 | return super.onCreateDialog(id); 364 | } 365 | 366 | @Override 367 | public final boolean onCreateOptionsMenu(final Menu menu) { 368 | super.onCreateOptionsMenu(menu); 369 | menu.add(0, MENU_LOG, 0, R.string.main_menu_log) 370 | .setIcon(android.R.drawable.ic_menu_info_details); 371 | 372 | menu.add(0, MENU_ABOUT, 0, R.string.main_menu_about) 373 | .setIcon(android.R.drawable.ic_menu_help); 374 | return true; 375 | } 376 | 377 | @Override 378 | public final boolean onOptionsItemSelected(final MenuItem item) { 379 | Intent intent; 380 | switch (item.getItemId()) { 381 | case MENU_LOG: 382 | intent = new Intent().setClass(TunnelListPreferences.this, 383 | Log.class); 384 | startActivity(intent); 385 | break; 386 | 387 | case MENU_ABOUT: 388 | Utils.showAboutBox(this); 389 | break; 390 | default: 391 | return false; 392 | } 393 | 394 | return true; 395 | } 396 | 397 | @Override 398 | public final void onTunnelConnect(final String name) { 399 | populateTunnels(); 400 | } 401 | 402 | @Override 403 | public final void onTunnelDisconnect(final String name) { 404 | populateTunnels(); 405 | } 406 | 407 | } 408 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | import java.net.Inet4Address; 25 | import java.net.Inet6Address; 26 | import java.net.InetAddress; 27 | import java.net.NetworkInterface; 28 | import java.net.SocketException; 29 | import java.net.UnknownHostException; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | import android.content.Context; 34 | import android.net.ConnectivityManager; 35 | import android.net.NetworkInfo; 36 | import android.net.wifi.WifiManager; 37 | import android.util.Log; 38 | 39 | /** 40 | * This class gathers network-related functions, 41 | * such as getting/setting routes, checking connectivity, etc. 42 | * @author Vitaly 43 | * 44 | */ 45 | public final class NetworkUtils { 46 | /** Logging tag. */ 47 | private static final String TAG = "NetworkUtils"; 48 | 49 | /** Size of the host route prefix. */ 50 | private static final int HOST_ROUTE_PREFIX_IPV4 = 32; 51 | 52 | /** Size of the host route prefix. */ 53 | private static final int HOST_ROUTE_PREFIX_IPV6 = 128; 54 | 55 | /** Mask for extracting the lowest byte. */ 56 | private static final int BYTE_MASK = 0xFF; 57 | 58 | /** This class is not supposed to be instantiated. */ 59 | private NetworkUtils() { 60 | 61 | } 62 | 63 | /** 64 | * Add a route to the routing table. 65 | * 66 | * @param interfaceName the interface to route through. 67 | * @param dst the network or host to route to. May be IPv4 or IPv6, e.g. 68 | * "0.0.0.0" or "2001:4860::". 69 | * @param prefixLength the prefix length of the route. 70 | * @param gw the gateway to use, e.g., "192.168.251.1". If null, 71 | * indicates a directly-connected route. 72 | * @return the status code of the command. 73 | */ 74 | public static int addRoute( 75 | final String interfaceName, 76 | final String dst, 77 | final int prefixLength, final String gw) { 78 | 79 | String cmd = "ip route add " + dst + "/" + prefixLength; 80 | if (gw != null) { 81 | cmd = cmd + " via " + gw; 82 | } 83 | cmd = cmd + " dev " + interfaceName; 84 | 85 | Commands cmds = new Commands(); 86 | return cmds.runCommandAsRootAndWait(cmd); 87 | } 88 | 89 | /** 90 | * Add a default route through the specified gateway. 91 | * @param interfaceName interface on which the route should be added 92 | * @param gw the IP address of the gateway to which the route is desired, 93 | * @return {@code true} on success, {@code false} on failure 94 | */ 95 | public static boolean addDefaultRoute( 96 | final String interfaceName, 97 | final InetAddress gw) { 98 | String dstStr; 99 | String gwStr = gw.getHostAddress(); 100 | 101 | if (gw instanceof Inet4Address) { 102 | dstStr = "0.0.0.0"; 103 | } else if (gw instanceof Inet6Address) { 104 | dstStr = "::"; 105 | } else { 106 | Log.w(TAG, "addDefaultRoute failure: " 107 | + "address is neither IPv4 nor IPv6" 108 | + "(" + gwStr + ")"); 109 | return false; 110 | } 111 | return addRoute(interfaceName, dstStr, 0, gwStr) == 0; 112 | } 113 | 114 | 115 | /** 116 | * Add a host route. 117 | * @param interfaceName interface on which the route should be added 118 | * @param dst the IP address of the host to which the route is desired, 119 | * this should not be null. 120 | * @param gw the IP address of the gateway to which the route is desired, 121 | * if null, indicates a directly-connected route. 122 | * @return {@code true} on success, {@code false} on failure 123 | */ 124 | public static boolean addHostRoute( 125 | final String interfaceName, 126 | final InetAddress dst, 127 | final InetAddress gw) { 128 | 129 | if (dst == null) { 130 | Log.w(TAG, "addHostRoute: dst should not be null"); 131 | return false; 132 | } 133 | 134 | int prefixLength; 135 | String dstStr = dst.getHostAddress(); 136 | String gwStr = null; 137 | 138 | if (gw != null) { 139 | gwStr = gw.getHostAddress(); 140 | } 141 | 142 | if (dst instanceof Inet4Address) { 143 | prefixLength = HOST_ROUTE_PREFIX_IPV4; 144 | } else if (dst instanceof Inet6Address) { 145 | prefixLength = HOST_ROUTE_PREFIX_IPV6; 146 | } else { 147 | Log.w(TAG, "addHostRoute failure: " 148 | + "address is neither IPv4 nor IPv6" 149 | + "(" + dst + ")"); 150 | return false; 151 | } 152 | return addRoute(interfaceName, dstStr, prefixLength, gwStr) == 0; 153 | } 154 | 155 | /** 156 | * Remove the default route for the named interface. 157 | * @param interfaceName The name of the interface. 158 | * @return the status of the command. 159 | */ 160 | public static int removeDefaultRoute(final String interfaceName) { 161 | String cmd = "ip route delete default dev " + interfaceName; 162 | Commands cmds = new Commands(); 163 | return cmds.runCommandAsRootAndWait(cmd); 164 | 165 | } 166 | 167 | /** 168 | * Delete all routes from the system. 169 | */ 170 | public static void removeAllRoutes() { 171 | Commands cmds = new Commands(); 172 | String cmd = "ip route flush table main"; 173 | cmds.runCommandAsRootAndWait(cmd); 174 | } 175 | 176 | /** 177 | * Converts a prefix length to a network mask. 178 | * For example, 24 would return 255.255.255.0. 179 | * @param length The prefix length. 180 | * @return The network mask. 181 | */ 182 | public static int prefixLengthToMask(final int length) { 183 | int mask = 0; 184 | for (int i = 0; i < length; ++i) { 185 | mask |= (1 << ((HOST_ROUTE_PREFIX_IPV4 - 1) - i)); 186 | } 187 | 188 | //Swap the bytes 189 | int result = 0; 190 | result |= (mask >>> 24) & BYTE_MASK; 191 | result |= ((mask >>> 16) & BYTE_MASK) << 8; 192 | result |= ((mask >>> 8) & BYTE_MASK) << 16; 193 | result |= ((mask) & BYTE_MASK) << 24; 194 | 195 | return result; 196 | } 197 | 198 | /** 199 | * Transforms a mask into a prefix length. 200 | * @param subnetMask The mask 201 | * @return The prefix length 202 | */ 203 | public static int maskToPrefixLength(final int subnetMask) { 204 | int mask = subnetMask; 205 | int length = 0; 206 | while (mask != 0) { 207 | if ((mask & 1) != 0) { 208 | length++; 209 | } 210 | mask = mask >>> 1; 211 | } 212 | return length; 213 | } 214 | 215 | /** 216 | * Activate the specified routes. 217 | * @param routes The list of routes to add. 218 | */ 219 | public static void restoreRoutes(final List routes) { 220 | for (RouteEntry re : routes) { 221 | String dst = intToInetAddress(re.getDestination()).getHostAddress(); 222 | String gw = null; 223 | if (re.getGateway() != 0) { 224 | gw = intToInetAddress(re.getGateway()).getHostAddress(); 225 | } 226 | 227 | addRoute(re.getInterfaceName(), dst, 228 | maskToPrefixLength(re.getMask()), gw); 229 | } 230 | } 231 | 232 | 233 | /** 234 | * Convert a IPv4 address from an integer to an InetAddress. 235 | * @param hostAddress is an Int corresponding to the IPv4 236 | * address in network byte order 237 | * @return the IP address as an {@code InetAddress}, returns null if 238 | * unable to convert or if the int is an invalid address. 239 | */ 240 | public static InetAddress intToInetAddress(final int hostAddress) { 241 | InetAddress inetAddress; 242 | byte[] addressBytes = { (byte)(BYTE_MASK & hostAddress), 243 | (byte)(BYTE_MASK & (hostAddress >> 8)), 244 | (byte)(BYTE_MASK & (hostAddress >> 16)), 245 | (byte)(BYTE_MASK & (hostAddress >> 24)) }; 246 | 247 | try { 248 | inetAddress = InetAddress.getByAddress(addressBytes); 249 | } catch (UnknownHostException e) { 250 | return null; 251 | } 252 | 253 | return inetAddress; 254 | } 255 | 256 | 257 | /** 258 | * Converts a string representation of an IPv4 address 259 | * into the equivalent integer. 260 | * @param str The string IP address. 261 | * @return The 32-bits address. 262 | */ 263 | public static int v4StringToInt(final String str) { 264 | int result = 0; 265 | String[] array = str.split("\\."); 266 | 267 | if (array.length != 4) { 268 | return 0; 269 | } 270 | 271 | try { 272 | result = Integer.parseInt(array[3]); 273 | result = (result << 8) + Integer.parseInt(array[2]); 274 | result = (result << 8) + Integer.parseInt(array[1]); 275 | result = (result << 8) + Integer.parseInt(array[0]); 276 | } catch (NumberFormatException e) { 277 | return 0; 278 | } 279 | return result; 280 | } 281 | 282 | 283 | 284 | 285 | /** 286 | * For the given interface name, look for the default route 287 | * in the list of routes. 288 | * @param entries The list of routes. 289 | * @param iface The insterface name. 290 | * @return The default route, if it exists. 291 | */ 292 | public static RouteEntry getDefaultRoute( 293 | final List entries, 294 | final String iface) { 295 | 296 | for (RouteEntry e : entries) { 297 | if (e.getDestination() == 0 && iface.equals(e.getInterfaceName())) { 298 | return e; 299 | } 300 | } 301 | return null; 302 | } 303 | 304 | /** 305 | * Get the system-wide default route. 306 | * @param entries The list of routes. 307 | * @return The default route, if it exists. 308 | */ 309 | public static RouteEntry getDefaultRoute(final List entries) { 310 | for (RouteEntry e : entries) { 311 | if (e.getDestination() == 0) { 312 | return e; 313 | } 314 | } 315 | return null; 316 | } 317 | 318 | /** 319 | * XXX: find other standard ways of doing it. 320 | * @return The DNS server address. 321 | */ 322 | public static InetAddress getDns() { 323 | String cmd = "getprop net.dns1"; 324 | 325 | Commands cmds = new Commands(); 326 | cmds.runCommandAsRoot(cmd); 327 | Process p = cmds.getProcess(); 328 | 329 | String line; 330 | BufferedReader in = 331 | new BufferedReader(new InputStreamReader(p.getInputStream())); 332 | 333 | try { 334 | line = in.readLine(); 335 | return InetAddress.getByName(line); 336 | } catch (Exception e) { 337 | return null; 338 | } 339 | } 340 | 341 | /** 342 | * Find the interface name of the Wifi interface. 343 | * @param ctx The Android context. 344 | * @return The name of the Wifi interface. 345 | */ 346 | public static String getWifiInterface(final Context ctx) { 347 | //Get the ip of the Wifi interface 348 | WifiManager wifi = 349 | (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE); 350 | 351 | int ip = wifi.getConnectionInfo().getIpAddress(); 352 | if (ip == 0) { 353 | return null; 354 | } 355 | 356 | InetAddress addr = intToInetAddress(ip); 357 | NetworkInterface ni; 358 | try { 359 | ni = NetworkInterface.getByInetAddress(addr); 360 | return ni.getName(); 361 | } catch (SocketException e) { 362 | // TODO Auto-generated catch block 363 | e.printStackTrace(); 364 | } 365 | return null; 366 | } 367 | 368 | /** 369 | * Retrieves all the network routes from the system. 370 | * @return The list of routes. 371 | */ 372 | public static List getRoutes() { 373 | List routes = new ArrayList(); 374 | Commands cmds = new Commands(); 375 | cmds.runCommandAsRoot("ip route"); 376 | 377 | String line; 378 | BufferedReader in = new BufferedReader( 379 | new InputStreamReader(cmds.getProcess().getInputStream())); 380 | 381 | try { 382 | while ((line = in.readLine()) != null) { 383 | //Get the destination 384 | RouteEntry re = RouteEntry.fromIpRouteCommand(line); 385 | if (re != null) { 386 | routes.add(re); 387 | } 388 | } 389 | } catch (IOException e) { 390 | return routes; 391 | } 392 | return routes; 393 | } 394 | 395 | /** 396 | * Get the interface name associated with the default route. 397 | * @return The interface name. 398 | */ 399 | public static String getDefaultRouteIface() { 400 | List routes = getRoutes(); 401 | RouteEntry re = getDefaultRoute(routes); 402 | if (re == null) { 403 | return null; 404 | } 405 | 406 | return re.getInterfaceName(); 407 | } 408 | 409 | /** 410 | * Check whether the specified interface exists. 411 | * @param iface The name of the interface. 412 | * @return true if the interface exists. 413 | */ 414 | public static boolean interfaceExists(final String iface) { 415 | boolean exists = false; 416 | try { 417 | exists = NetworkInterface.getByName(iface) != null; 418 | } catch (SocketException e) { 419 | return false; 420 | } 421 | return exists; 422 | } 423 | 424 | /** 425 | * Verifies that Internet is reachable. 426 | * @param interfaceName The name of the network interface. 427 | * @return whether the default route on interfaceName exists 428 | */ 429 | public static boolean checkRoutes(final String interfaceName) { 430 | List routes = getRoutes(); 431 | RouteEntry oldDefaultRoute = 432 | getDefaultRoute(routes, interfaceName); 433 | 434 | if (oldDefaultRoute == null) { 435 | return false; 436 | } 437 | return true; 438 | } 439 | 440 | /** 441 | * @param ctx The Android context. 442 | * @return Whether WIFI or Data connection is enabled 443 | */ 444 | public static boolean checkConnectivity(final Context ctx) { 445 | ConnectivityManager mgr = (ConnectivityManager) ctx.getSystemService( 446 | Context.CONNECTIVITY_SERVICE); 447 | 448 | 449 | NetworkInfo infoWifi, infoMobile; 450 | 451 | infoWifi = mgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 452 | if (infoWifi != null) { 453 | if (infoWifi.isConnected()) { 454 | return true; 455 | } 456 | } 457 | 458 | infoMobile = mgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); 459 | if (infoMobile != null) { 460 | if (infoMobile.isConnected()) { 461 | return true; 462 | } 463 | } 464 | 465 | return false; 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /src/net/magictunnel/core/Iodine.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel.core; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.InputStream; 23 | import java.io.InputStreamReader; 24 | import java.net.InetAddress; 25 | import java.net.NetworkInterface; 26 | import java.net.SocketException; 27 | import java.net.UnknownHostException; 28 | import java.util.ArrayList; 29 | import java.util.Enumeration; 30 | import java.util.List; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | import net.magictunnel.R; 35 | import net.magictunnel.Utils; 36 | import net.magictunnel.settings.DnsProtocol; 37 | import net.magictunnel.settings.DnsRawConnection; 38 | import net.magictunnel.settings.Profile; 39 | import android.app.ProgressDialog; 40 | import android.content.Context; 41 | import android.os.AsyncTask; 42 | import android.widget.Toast; 43 | 44 | /** 45 | * This class interfaces with the Iodine client. 46 | * @author Vitaly 47 | * 48 | */ 49 | public class Iodine { 50 | /** 51 | * Pattern to extract the address of the remote DNS tunnel server 52 | * in case direct connection is possible. 53 | */ 54 | private static final Pattern IODINE_RAW_PATTERN = 55 | Pattern.compile("directly to\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+)"); 56 | 57 | /** Pattern to extract the address of the DNS tunnel server. */ 58 | private static final Pattern IODINE_SERVER_TUNNEL_PATTERN = 59 | Pattern.compile("Server tunnel IP is\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+)"); 60 | 61 | 62 | /** How many lines to display at once in the progress dialog. */ 63 | private static final int MAX_PROGRESS_LINES = 5; 64 | 65 | /** How much time to wait for certain commands. */ 66 | private static final int SLEEP_TIME = 1000; 67 | 68 | /** The name of the profile currently running. */ 69 | private Profile mActiveProfile; 70 | 71 | /** For issuing commands. */ 72 | private Commands mCmds; 73 | 74 | /** The current Android context. */ 75 | private Context mContext; 76 | 77 | //TODO clear the saved routes when connectivity changes 78 | /** Routes that were active before Ioding was enabled. */ 79 | private List mSavedRoutes; 80 | 81 | /** The log output of the connection process. */ 82 | private StringBuffer mLog = new StringBuffer(); 83 | 84 | 85 | /** 86 | * All the folks interested in being notified 87 | * of connectivity changes. 88 | */ 89 | private ArrayList mListeners = 90 | new ArrayList(); 91 | 92 | /** 93 | * Create the interface with the Ioding client. 94 | */ 95 | public Iodine() { 96 | mCmds = new Commands(); 97 | } 98 | 99 | /** 100 | * Initialize the Android context. 101 | * @param ctx The context. 102 | */ 103 | public final void setContext(final Context ctx) { 104 | mContext = ctx; 105 | } 106 | 107 | /** 108 | * @return The log buffer. 109 | */ 110 | public final StringBuffer getLog() { 111 | return mLog; 112 | } 113 | 114 | /** 115 | * @return The current active profile. 116 | */ 117 | public final Profile getActiveProfile() { 118 | return mActiveProfile; 119 | } 120 | 121 | /** 122 | * @return Whether the Iodine client is running or not. 123 | */ 124 | public final boolean isIodineRunning() { 125 | return Commands.isProgramRunning("iodine"); 126 | } 127 | 128 | /** Reset all saved routes (e.g., when they become invalid). */ 129 | public final void resetSavedRoutes() { 130 | mSavedRoutes = null; 131 | } 132 | 133 | /** 134 | * Reroutes the traffic through the tunnel. 135 | * @param transportInterface is either wifi or cellular NIC. 136 | * 137 | * @param tunnelEntry is the address where data 138 | * gets tunneled (e.g., ISP's DNS server 139 | * or Iodine server in case of raw tunnel). 140 | * 141 | * @param serverTunnelIp is the IP address of the tunnels 142 | * server's endpoint (e.g., 192.168.123.3) 143 | * 144 | * @return success status. 145 | */ 146 | public final boolean setupRoute( 147 | final String transportInterface, 148 | final InetAddress tunnelEntry, 149 | final InetAddress serverTunnelIp) { 150 | 151 | NetworkInterface ni; 152 | 153 | try { 154 | ni = NetworkInterface.getByName("dns0"); 155 | } catch (SocketException e) { 156 | return false; 157 | } 158 | 159 | Enumeration addresses = ni.getInetAddresses(); 160 | if (!addresses.hasMoreElements()) { 161 | return false; 162 | } 163 | 164 | mSavedRoutes = NetworkUtils.getRoutes(); 165 | RouteEntry oldDefaultRoute = 166 | NetworkUtils.getDefaultRoute(mSavedRoutes, transportInterface); 167 | 168 | if (oldDefaultRoute == null) { 169 | return false; 170 | } 171 | 172 | InetAddress oldDefaultGateway = 173 | NetworkUtils.intToInetAddress(oldDefaultRoute.getGateway()); 174 | 175 | NetworkUtils.removeDefaultRoute(transportInterface); 176 | 177 | NetworkUtils.addHostRoute(transportInterface, 178 | tunnelEntry, oldDefaultGateway); 179 | 180 | NetworkUtils.addDefaultRoute("dns0", serverTunnelIp); 181 | 182 | return true; 183 | } 184 | 185 | /** 186 | * Sets up the most optimal route depending on whether or not 187 | * raw connections are accepted by the network. 188 | * @param transportInterface Where to route the traffic through. 189 | * @return the success status. 190 | */ 191 | public final boolean setupRoute(final String transportInterface) { 192 | InetAddress serverTunnelIp = getServerTunnelIp(mLog); 193 | if (serverTunnelIp == null) { 194 | return false; 195 | } 196 | 197 | InetAddress raw = getRawEndpoint(mLog); 198 | if (raw != null) { 199 | return setupRoute(transportInterface, raw, serverTunnelIp); 200 | } else { 201 | InetAddress dnsIspServer = NetworkUtils.getDns(); 202 | if (dnsIspServer == null) { 203 | return false; 204 | } 205 | return setupRoute(transportInterface, dnsIspServer, serverTunnelIp); 206 | } 207 | } 208 | 209 | /** 210 | * Extracts the raw endpoint from the Iodine log messages. 211 | * @param log The output generated by the Iodine client. 212 | * @param p The pattern for IP extraction. 213 | * @return The IP address. 214 | */ 215 | final InetAddress extractIpFromLog( 216 | final StringBuffer log, 217 | final Pattern p) { 218 | Matcher m = p.matcher(log.toString()); 219 | 220 | if (!m.find()) { 221 | return null; 222 | } 223 | String addr = m.group(1); 224 | try { 225 | return InetAddress.getByName(addr); 226 | } catch (UnknownHostException e) { 227 | return null; 228 | } 229 | } 230 | 231 | /** 232 | * Extract the raw endpoint of the DNS tunnel, in case the ISP allows 233 | * direct UDP traffic to the DNS tunnel server. 234 | * @param log The output generated by the Iodine client. 235 | * @return The IP address. 236 | */ 237 | final InetAddress getRawEndpoint(final StringBuffer log) { 238 | return extractIpFromLog(log, IODINE_RAW_PATTERN); 239 | } 240 | 241 | /** 242 | * Extract IP address of the DNS tunnel server. 243 | * @param log The output generated by the Iodine client. 244 | * @return The IP address. 245 | */ 246 | final InetAddress getServerTunnelIp(final StringBuffer log) { 247 | return extractIpFromLog(log, IODINE_SERVER_TUNNEL_PATTERN); 248 | } 249 | 250 | /** 251 | * Build a command line to launch the Iodine client. 252 | * This command line is determined by the profile settings. 253 | * @param p The profile. 254 | * @return The command string. 255 | */ 256 | private StringBuilder buildCommandLine(final Profile p) { 257 | StringBuilder cmdBuilder = new StringBuilder(); 258 | cmdBuilder.append("iodine"); 259 | 260 | if (p.getPassword() != null) { 261 | cmdBuilder.append(" -P "); 262 | cmdBuilder.append(p.getPassword()); 263 | } 264 | 265 | if (p.getPacketSize() > 0) { 266 | cmdBuilder.append(" -m"); 267 | cmdBuilder.append(Integer.toString(p.getPacketSize())); 268 | } 269 | 270 | if (!p.getDnsProtocol().equals(DnsProtocol.AUTODETECT)) { 271 | cmdBuilder.append(" -T"); 272 | cmdBuilder.append(p.getDnsProtocol().toString()); 273 | } 274 | 275 | if (p.getRawConnection().equals(DnsRawConnection.NO)) { 276 | cmdBuilder.append(" -r"); 277 | } 278 | 279 | cmdBuilder.append(" -d dns0 "); 280 | cmdBuilder.append(p.getDomainName()); 281 | return cmdBuilder; 282 | } 283 | 284 | 285 | /** 286 | * Disconnects the tunnel. 287 | */ 288 | public final void disconnect() { 289 | if (mActiveProfile == null) { 290 | return; 291 | } 292 | 293 | if (mSavedRoutes != null) { 294 | NetworkUtils.removeAllRoutes(); 295 | NetworkUtils.restoreRoutes(mSavedRoutes); 296 | mSavedRoutes = null; 297 | } 298 | 299 | mCmds.runCommandAsRoot("killall -9 iodine > /dev/null"); 300 | try { 301 | Thread.sleep(SLEEP_TIME); 302 | } catch (InterruptedException e) { 303 | // TODO Auto-generated catch block 304 | e.printStackTrace(); 305 | } 306 | showConnectionToast(R.string.iodine_notify_disconnected); 307 | broadcastOnTunnelDisconnect(mActiveProfile.getName()); 308 | mActiveProfile = null; 309 | } 310 | 311 | /** 312 | * Show a message toast on the screen. 313 | * @param messageId The ID of the message to show. 314 | */ 315 | private void showConnectionToast(final int messageId) { 316 | String text = mContext.getString(messageId); 317 | int duration = Toast.LENGTH_LONG; 318 | 319 | Toast toast = Toast.makeText(mContext, text, duration); 320 | toast.show(); 321 | } 322 | 323 | 324 | /** 325 | * Register a listener for connect/disconnect events. 326 | * @param listener The listener. 327 | */ 328 | public final void registerListener(final ITunnelStatusListener listener) { 329 | if (mListeners.contains(listener)) { 330 | return; 331 | } 332 | mListeners.add(listener); 333 | } 334 | 335 | /** 336 | * Unregister a listener for connect/disconnect events. 337 | * @param listener The listener. 338 | */ 339 | public final void unregisterListener(final ITunnelStatusListener listener) { 340 | mListeners.remove(listener); 341 | } 342 | 343 | /** 344 | * Notifies all listeners that the tunnel got connected. 345 | * @param name The name of the profile. 346 | */ 347 | private void broadcastOnTunnelConnect(final String name) { 348 | for (ITunnelStatusListener l : mListeners) { 349 | l.onTunnelConnect(name); 350 | } 351 | } 352 | 353 | /** 354 | * Notifies all listeners that the tunnel got disconnected. 355 | * @param name The name of the profile. 356 | */ 357 | private void broadcastOnTunnelDisconnect(final String name) { 358 | for (ITunnelStatusListener l : mListeners) { 359 | l.onTunnelDisconnect(name); 360 | } 361 | } 362 | 363 | 364 | /** 365 | * An AsyncTask can be used only once. Therefore, we have 366 | * to recreate a new one on each use. 367 | * @param p The profile to activate. 368 | * @return The launcher task. 369 | */ 370 | public final LauncherTask getLauncher(final Profile p) { 371 | return new LauncherTask(p); 372 | } 373 | 374 | /** 375 | * Asynchronous task to launch the Iodine client. 376 | * @author Vitaly 377 | * 378 | */ 379 | public final class LauncherTask extends AsyncTask { 380 | /** The progress dialog. */ 381 | private ProgressDialog mProgress; 382 | 383 | /** List of messages to display in the progress dialog. */ 384 | private ArrayList mMessages = new ArrayList(); 385 | 386 | /** The profile to activate. */ 387 | private Profile mProfile; 388 | 389 | /** 390 | * Create a new launcher task. 391 | * @param p The profile to activate. 392 | */ 393 | private LauncherTask(final Profile p) { 394 | mProfile = p; 395 | } 396 | 397 | /** 398 | * Launch the iodine process. 399 | * @throws InterruptedException if the task is interrupted. 400 | */ 401 | private void launch() throws InterruptedException { 402 | publishProgress("Killing previous instance of iodine..."); 403 | mCmds.runCommandAsRoot("killall -9 iodine > /dev/null"); 404 | Thread.sleep(SLEEP_TIME); 405 | 406 | mCmds.runCommandAsRoot(buildCommandLine(mProfile).toString()); 407 | pollProgress(mCmds.getProcess().getErrorStream()); 408 | } 409 | 410 | 411 | /** 412 | * Check if Iodine generated additional output and display it 413 | * on the progress status dialog. 414 | * @param is The input stream of the process. 415 | */ 416 | private void pollProgress(final InputStream is) { 417 | BufferedReader in = new BufferedReader(new InputStreamReader(is)); 418 | try { 419 | String l; 420 | while ((l = in.readLine()) != null) { 421 | publishProgress(l); 422 | } 423 | } catch (Exception e) { 424 | return; 425 | } 426 | } 427 | 428 | /** 429 | * Determines the interface through which all traffic goes. 430 | * @return the interface name. 431 | */ 432 | private String getActiveInterface() { 433 | return NetworkUtils.getDefaultRouteIface(); 434 | } 435 | 436 | @Override 437 | protected void onPreExecute() { 438 | super.onPreExecute(); 439 | 440 | if (!NetworkUtils.checkConnectivity(mContext)) { 441 | Utils.showErrorMessage(mContext, 442 | R.string.iodine_no_connectivity, 443 | R.string.iodine_enable_wifi_or_mobile); 444 | cancel(true); 445 | return; 446 | } 447 | 448 | if (!NetworkUtils.checkRoutes(getActiveInterface())) { 449 | Utils.showErrorMessage(mContext, 450 | R.string.iodine_no_route, 451 | R.string.iodine_cycle_connection); 452 | cancel(true); 453 | return; 454 | } 455 | 456 | mLog = new StringBuffer(); 457 | mProgress = new ProgressDialog(mContext); 458 | mProgress.setCancelable(false); 459 | mProgress.setProgressStyle(ProgressDialog.STYLE_SPINNER); 460 | mProgress.setMessage("Connecting..."); 461 | mProgress.show(); 462 | } 463 | 464 | @Override 465 | protected Boolean doInBackground(final Void... arg0) { 466 | try { 467 | launch(); 468 | Thread.sleep(SLEEP_TIME); 469 | } catch (Exception e) { 470 | e.printStackTrace(); 471 | } 472 | return true; 473 | } 474 | 475 | @Override 476 | protected void onPostExecute(final Boolean result) { 477 | super.onPostExecute(result); 478 | mProgress.cancel(); 479 | 480 | if (!NetworkUtils.interfaceExists("dns0")) { 481 | Utils.showErrorMessage(mContext, 482 | R.string.iodine_no_connectivity, 483 | R.string.iodine_check_dns); 484 | return; 485 | } 486 | 487 | String iface = getActiveInterface(); 488 | 489 | if (!setupRoute(iface)) { 490 | Utils.showErrorMessage(mContext, 491 | R.string.iodine_no_connectivity, 492 | R.string.iodine_routing_error); 493 | return; 494 | } 495 | 496 | showConnectionToast(R.string.iodine_notify_connected); 497 | mActiveProfile = mProfile; 498 | broadcastOnTunnelConnect(mProfile.getName()); 499 | } 500 | 501 | @Override 502 | protected void onProgressUpdate(final String... values) { 503 | super.onProgressUpdate(values); 504 | mLog.append(values[0]); 505 | mLog.append("\n"); 506 | mMessages.add(values[0]); 507 | 508 | if (mMessages.size() > MAX_PROGRESS_LINES) { 509 | mMessages.remove(0); 510 | } 511 | 512 | StringBuffer buf = new StringBuffer(); 513 | for (String s : mMessages) { 514 | buf.append(s + "\n"); 515 | } 516 | 517 | mProgress.setMessage(buf.toString()); 518 | } 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/net/magictunnel/TunnelPreferences.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MagicTunnel DNS tunnel GUI for Android. 3 | * Copyright (C) 2011 Vitaly Chipounov 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | package net.magictunnel; 20 | 21 | import java.util.ArrayList; 22 | 23 | import net.magictunnel.settings.DnsProtocol; 24 | import net.magictunnel.settings.DnsRawConnection; 25 | import net.magictunnel.settings.Profile; 26 | import net.magictunnel.settings.Settings; 27 | import android.app.AlertDialog; 28 | import android.app.Dialog; 29 | import android.content.DialogInterface; 30 | import android.os.Bundle; 31 | import android.preference.CheckBoxPreference; 32 | import android.preference.EditTextPreference; 33 | import android.preference.ListPreference; 34 | import android.preference.Preference; 35 | import android.preference.Preference.OnPreferenceChangeListener; 36 | import android.preference.PreferenceActivity; 37 | import android.preference.PreferenceCategory; 38 | import android.preference.PreferenceScreen; 39 | import android.text.InputType; 40 | import android.text.method.PasswordTransformationMethod; 41 | import android.view.KeyEvent; 42 | import android.view.Menu; 43 | import android.view.MenuItem; 44 | 45 | /** 46 | * Activity for changing tunnel settings. 47 | * @author Vitaly 48 | * 49 | */ 50 | public class TunnelPreferences extends PreferenceActivity { 51 | 52 | /** Save menu ID. */ 53 | private static final int MENU_SAVE = Menu.FIRST; 54 | 55 | /** Cancel menu ID. */ 56 | private static final int MENU_CANCEL = Menu.FIRST + 1; 57 | 58 | /** Confirmation dialog ID. */ 59 | private static final int CONFIRM_DIALOG_ID = 0; 60 | 61 | /** The recorded profile name. */ 62 | private String mName = ""; 63 | 64 | /** The recorded domain name. */ 65 | private String mDomain = ""; 66 | 67 | /** The recorded password. */ 68 | private String mPassword = ""; 69 | 70 | /** 71 | * Packet size to use when transmitting queries. 72 | * Zero for auto-detection. 73 | */ 74 | private int mPacketSize = 0; 75 | 76 | /** 77 | * Speed up connection by skipping the detection 78 | * of whether it is possible to connect to the DNS 79 | * server directly. Since most of the time it is not 80 | * possible, this option speeds up connection. 81 | */ 82 | private DnsRawConnection mDoRawConnectionDetection = DnsRawConnection.AUTODETECT; 83 | 84 | /** 85 | * Specifies which protocol to use for tunneling data 86 | * over the DNS tunnel. 87 | */ 88 | private DnsProtocol mDnsProtocol = DnsProtocol.AUTODETECT; 89 | 90 | /** The text box for the profile name. */ 91 | private EditTextPreference mPrefName; 92 | 93 | /** The text box for the domain name. */ 94 | private EditTextPreference mPrefDomain; 95 | 96 | /** The text box for the password. */ 97 | private EditTextPreference mPrefPassword; 98 | 99 | /** The text box for maximum packet size. */ 100 | private EditTextPreference mPrefMaxPacketSize; 101 | 102 | /** The list for raw connection detection. */ 103 | private ListPreference mPrefDoRawDetection; 104 | 105 | /** The list of supported protocols. */ 106 | private ListPreference mPrefDnsProtocol; 107 | 108 | /** Specifies whether the profile being edited already exists or not. */ 109 | private boolean mNew; 110 | 111 | /** The settings repository. */ 112 | private Settings mSettings; 113 | 114 | /** The profile being edited. */ 115 | private Profile mProfile; 116 | 117 | @Override 118 | protected final void onCreate(final Bundle savedInstanceState) { 119 | super.onCreate(savedInstanceState); 120 | addPreferencesFromResource(R.xml.tunnelsettings); 121 | 122 | populatePreferenceScreen(); 123 | fillInProperties(); 124 | } 125 | 126 | /** 127 | * Retrieves the profile from the settings and fills in 128 | * the edit fields. 129 | */ 130 | private void fillInProperties() { 131 | MagicTunnel app = (MagicTunnel) getApplication(); 132 | mSettings = app.getSettings(); 133 | String curProfile = mSettings.getCurrentSettingsProfile(); 134 | if (curProfile.equals("")) { 135 | mNew = true; 136 | mProfile = new Profile(); 137 | return; 138 | } 139 | 140 | Profile prof = mSettings.getProfile(curProfile); 141 | if (prof == null) { 142 | throw new RuntimeException("Could not retrive profile"); 143 | } 144 | 145 | /********************/ 146 | mName = prof.getName(); 147 | mPrefName.getEditText().setText(prof.getName()); 148 | mPrefName.setSummary(prof.getName()); 149 | 150 | /********************/ 151 | mDomain = prof.getDomainName(); 152 | mPrefDomain.getEditText().setText(prof.getDomainName()); 153 | mPrefDomain.setSummary(prof.getDomainName()); 154 | 155 | /********************/ 156 | mPassword = prof.getPassword(); 157 | mPrefPassword.getEditText().setText(prof.getPassword()); 158 | 159 | if (mPassword.length() == 0) { 160 | mPrefPassword.setSummary(R.string.profile_password_not_set); 161 | mPrefPassword.getEditText() 162 | .setHint(R.string.profile_password_not_set); 163 | } else { 164 | mPrefPassword.setSummary(R.string.profile_password_unchanged); 165 | mPrefPassword.getEditText() 166 | .setHint(R.string.profile_password_unchanged); 167 | } 168 | 169 | /********************/ 170 | mPacketSize = prof.getPacketSize(); 171 | mPrefMaxPacketSize.getEditText().setText(Integer.toString(mPacketSize)); 172 | 173 | if (mPacketSize == 0) { 174 | mPrefMaxPacketSize.setSummary(R.string.autodetect); 175 | } else { 176 | mPrefMaxPacketSize.setSummary(Integer.toString(mPacketSize) + " bytes"); 177 | } 178 | 179 | /********************/ 180 | mDoRawConnectionDetection = prof.getRawConnection(); 181 | mPrefDoRawDetection.setValue(mDoRawConnectionDetection.toString()); 182 | String summary = mapListKeyToValue(mPrefDoRawDetection, mDoRawConnectionDetection.toString()); 183 | mPrefDoRawDetection.setSummary(summary); 184 | 185 | 186 | /********************/ 187 | mDnsProtocol = prof.getDnsProtocol(); 188 | mPrefDnsProtocol.setValue(mDnsProtocol.toString()); 189 | summary = mapListKeyToValue(mPrefDnsProtocol, mDnsProtocol.toString()); 190 | mPrefDnsProtocol.setSummary(summary); 191 | 192 | mProfile = prof; 193 | } 194 | 195 | /** 196 | * Checks whether all fields are properly set. 197 | * @return The message in case of an error. 198 | */ 199 | private String validate() { 200 | if (!mName.equals(mProfile.getName())) { 201 | Profile prof = mSettings.getProfile(mName); 202 | if (prof != null) { 203 | return getString(R.string.profile_exists); 204 | } 205 | } 206 | 207 | if (mName.equals("")) { 208 | return getString(R.string.profile_enter_name); 209 | } 210 | 211 | if (mDomain.equals("")) { 212 | return getString(R.string.profile_enter_domain); 213 | } 214 | 215 | return null; 216 | } 217 | 218 | /** 219 | * Saves the modification into the settings store. 220 | */ 221 | private void saveProperties() { 222 | mProfile.setDomainName(mDomain); 223 | mProfile.setPassword(mPassword); 224 | mProfile.setRawConnection(mDoRawConnectionDetection); 225 | mProfile.setPacketSize(mPacketSize); 226 | mProfile.setDnsProtocl(mDnsProtocol); 227 | 228 | if (mNew) { 229 | mProfile.setName(mName); 230 | mSettings.addProfile(mProfile); 231 | mProfile.saveProfile(this); 232 | } else if (!mProfile.getName().equals(mName)) { 233 | mSettings.rename(this, mProfile.getName(), mName); 234 | } else { 235 | mProfile.saveProfile(this); 236 | } 237 | } 238 | 239 | /** 240 | * 241 | * @return Whether the user made any changes to the profile. 242 | */ 243 | private boolean profileChanged() { 244 | return !mDomain.equals(mProfile.getDomainName()) 245 | || !mPassword.equals(mProfile.getPassword()) 246 | || !mName.equals(mProfile.getName()) 247 | || mPacketSize != mProfile.getPacketSize() 248 | || !mDoRawConnectionDetection.equals(mProfile.getRawConnection()) 249 | || !mDnsProtocol.equals(mProfile.getDnsProtocol()); 250 | } 251 | 252 | /** 253 | * Creates the edit fields on the screen. 254 | */ 255 | private void populatePreferenceScreen() { 256 | PreferenceScreen screen = getPreferenceScreen(); 257 | 258 | PreferenceCategory cat = new PreferenceCategory(this); 259 | String catName = getString(R.string.dns_tunnel_settings); 260 | cat.setTitle(catName); 261 | screen.addPreference(cat); 262 | 263 | mPrefName = createNamePreference(); 264 | screen.addPreference(mPrefName); 265 | 266 | mPrefDomain = createDomainPreference(); 267 | screen.addPreference(mPrefDomain); 268 | 269 | mPrefPassword = createPasswordPreference(); 270 | screen.addPreference(mPrefPassword); 271 | mPrefPassword.setSummary(R.string.profile_password_not_set); 272 | mPrefPassword.getEditText().setHint(R.string.profile_password_not_set); 273 | 274 | mPrefDnsProtocol = createProtocolPreference(); 275 | screen.addPreference(mPrefDnsProtocol); 276 | 277 | mPrefMaxPacketSize = createPacketSizePreference(); 278 | screen.addPreference(mPrefMaxPacketSize); 279 | 280 | mPrefDoRawDetection = createConnectionDetectionPreference(); 281 | screen.addPreference(mPrefDoRawDetection); 282 | } 283 | 284 | /** 285 | * @return The profile name edit field. 286 | */ 287 | private EditTextPreference createNamePreference() { 288 | EditTextPreference prefName = new EditTextPreference(this); 289 | prefName.setTitle(R.string.profile_name); 290 | prefName.setDialogTitle(R.string.profile_name); 291 | prefName.getEditText().setInputType( 292 | prefName.getEditText().getInputType() 293 | & ~InputType.TYPE_TEXT_FLAG_MULTI_LINE); 294 | 295 | prefName.setOnPreferenceChangeListener( 296 | new OnPreferenceChangeListener() { 297 | @Override 298 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 299 | mName = ((String) newValue).trim(); 300 | preference.setSummary(mName); 301 | return true; 302 | } 303 | }); 304 | return prefName; 305 | } 306 | 307 | /** 308 | * @return The domain name edit field. 309 | */ 310 | private EditTextPreference createDomainPreference() { 311 | EditTextPreference prefDomain = new EditTextPreference(this); 312 | prefDomain.setTitle(R.string.domain_name); 313 | prefDomain.setDialogTitle(R.string.domain_name); 314 | 315 | prefDomain.getEditText().setInputType( 316 | prefDomain.getEditText().getInputType() 317 | & ~InputType.TYPE_TEXT_FLAG_MULTI_LINE); 318 | 319 | prefDomain.setOnPreferenceChangeListener( 320 | new OnPreferenceChangeListener() { 321 | @Override 322 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 323 | mDomain = ((String) newValue).trim(); 324 | preference.setSummary(mDomain); 325 | return true; 326 | } 327 | }); 328 | return prefDomain; 329 | } 330 | 331 | /** 332 | * @return The password edit field. 333 | */ 334 | private EditTextPreference createPasswordPreference() { 335 | EditTextPreference prefPassword = new EditTextPreference(this); 336 | prefPassword.setTitle(R.string.password); 337 | prefPassword.setDialogTitle(R.string.password); 338 | prefPassword.getEditText().setInputType( 339 | InputType.TYPE_TEXT_VARIATION_PASSWORD); 340 | prefPassword.getEditText().setTransformationMethod( 341 | new PasswordTransformationMethod()); 342 | prefPassword.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 343 | @Override 344 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 345 | mPassword = ((String) newValue); 346 | EditTextPreference prefPassword = (EditTextPreference) preference; 347 | mPrefPassword.setSummary(R.string.profile_password_changed); 348 | prefPassword.getEditText().setHint(""); 349 | return true; 350 | } 351 | }); 352 | 353 | return prefPassword; 354 | } 355 | 356 | /** 357 | * @return The packet size field. 358 | */ 359 | private EditTextPreference createPacketSizePreference() { 360 | EditTextPreference packetSizePreference = new EditTextPreference(this); 361 | 362 | packetSizePreference.setTitle(R.string.packet_size); 363 | packetSizePreference.setDialogTitle(R.string.packet_size); 364 | packetSizePreference.getEditText().setInputType( 365 | InputType.TYPE_CLASS_NUMBER); 366 | packetSizePreference.setSummary(R.string.autodetect); 367 | 368 | packetSizePreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 369 | @Override 370 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 371 | try { 372 | mPacketSize = Integer.parseInt((String) newValue); 373 | } catch (NumberFormatException e) { 374 | mPacketSize = 0; 375 | } 376 | 377 | if (mPacketSize == 0) { 378 | mPrefMaxPacketSize.setSummary(R.string.autodetect); 379 | } else { 380 | mPrefMaxPacketSize.setSummary(Integer.toString(mPacketSize) + " bytes"); 381 | } 382 | return true; 383 | } 384 | }); 385 | 386 | return packetSizePreference; 387 | } 388 | 389 | /** 390 | * Maps a key to a displayable value. 391 | * @param pref The preference list. 392 | * @param key The key corresponding to the value we want. 393 | * @return The value. 394 | */ 395 | private static String mapListKeyToValue(ListPreference pref, String key) { 396 | CharSequence[] entryValues = pref.getEntryValues(); 397 | for (int index = 0; index < entryValues.length; ++index) { 398 | if (entryValues[index].toString().equals(key)) { 399 | return pref.getEntries()[index].toString(); 400 | } 401 | } 402 | return null; 403 | } 404 | 405 | /** 406 | * @return The raw connection detection check box. 407 | */ 408 | private ListPreference createConnectionDetectionPreference() { 409 | ListPreference detectionPreference = new ListPreference(this); 410 | 411 | detectionPreference.setTitle(R.string.raw_detection_short); 412 | detectionPreference.setDialogTitle(R.string.raw_detection_title); 413 | 414 | detectionPreference.setEntries(R.array.direct_connection_values); 415 | detectionPreference.setEntryValues(R.array.direct_connection_keys); 416 | 417 | String summary = mapListKeyToValue(detectionPreference, mDoRawConnectionDetection.toString()); 418 | detectionPreference.setSummary(summary); 419 | 420 | detectionPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 421 | @Override 422 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 423 | ListPreference listPref = (ListPreference) preference; 424 | CharSequence value = (CharSequence) newValue; 425 | mDoRawConnectionDetection = DnsRawConnection.valueOf(value.toString()); 426 | 427 | String summary = mapListKeyToValue(listPref, value.toString()); 428 | preference.setSummary(summary); 429 | return true; 430 | } 431 | }); 432 | 433 | return detectionPreference; 434 | } 435 | 436 | /** 437 | * @return The raw connection detection check box. 438 | */ 439 | private ListPreference createProtocolPreference() { 440 | ListPreference protocolPreference = new ListPreference(this); 441 | 442 | protocolPreference.setTitle(R.string.protocol_type); 443 | protocolPreference.setDialogTitle(R.string.protocol_type); 444 | 445 | protocolPreference.setEntries(R.array.protocol_values); 446 | protocolPreference.setEntryValues(R.array.protocol_keys); 447 | 448 | String summary = mapListKeyToValue(protocolPreference, mDnsProtocol.toString()); 449 | protocolPreference.setSummary(summary); 450 | 451 | protocolPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { 452 | @Override 453 | public boolean onPreferenceChange(final Preference preference, final Object newValue) { 454 | ListPreference listPref = (ListPreference) preference; 455 | CharSequence value = (CharSequence) newValue; 456 | mDnsProtocol = DnsProtocol.valueOf(value.toString()); 457 | 458 | String summary = mapListKeyToValue(listPref, value.toString()); 459 | preference.setSummary(summary); 460 | return true; 461 | } 462 | }); 463 | 464 | return protocolPreference; 465 | } 466 | 467 | @Override 468 | public final boolean onCreateOptionsMenu(final Menu menu) { 469 | super.onCreateOptionsMenu(menu); 470 | menu.add(0, MENU_SAVE, 0, R.string.save).setIcon(android.R.drawable.ic_menu_save); 471 | menu.add(0, MENU_CANCEL, 0, R.string.cancel).setIcon(android.R.drawable.ic_menu_close_clear_cancel); 472 | return true; 473 | } 474 | 475 | @Override 476 | public final boolean onKeyDown(final int keyCode, final KeyEvent event) { 477 | switch (keyCode) { 478 | case KeyEvent.KEYCODE_BACK: 479 | return doCancel(); 480 | default: 481 | break; 482 | } 483 | return super.onKeyDown(keyCode, event); 484 | } 485 | 486 | /** 487 | * Checks whether the changes are valid and saves them. 488 | * @return true if successfully saved. 489 | */ 490 | public final boolean validateAndSaveResult() { 491 | String error = validate(); 492 | if (error != null) { 493 | Utils.showErrorMessage(this, error); 494 | return false; 495 | } else { 496 | saveProperties(); 497 | } 498 | return true; 499 | } 500 | 501 | /** 502 | * Cancels the changes after showing a confirmation dialog 503 | * in case something was modified. 504 | * @return true. 505 | */ 506 | public final boolean doCancel() { 507 | if (profileChanged()) { 508 | showDialog(CONFIRM_DIALOG_ID); 509 | } else { 510 | finish(); 511 | } 512 | return true; 513 | } 514 | 515 | @Override 516 | public final boolean onOptionsItemSelected(final MenuItem item) { 517 | switch (item.getItemId()) { 518 | case MENU_SAVE: 519 | if (validateAndSaveResult()) { 520 | finish(); 521 | } 522 | return true; 523 | 524 | case MENU_CANCEL: 525 | return doCancel(); 526 | 527 | default: 528 | break; 529 | } 530 | return super.onOptionsItemSelected(item); 531 | } 532 | 533 | 534 | @Override 535 | protected final Dialog onCreateDialog(final int id) { 536 | 537 | if (id == CONFIRM_DIALOG_ID) { 538 | int messageId = R.string.confirm_edit_profile_cancellation; 539 | if (mNew) { 540 | messageId = R.string.confirm_add_profile_cancellation; 541 | } 542 | 543 | return new AlertDialog.Builder(this) 544 | .setTitle(android.R.string.dialog_alert_title) 545 | .setIcon(android.R.drawable.ic_dialog_alert) 546 | .setMessage(messageId) 547 | .setPositiveButton( 548 | R.string.yes, 549 | new DialogInterface.OnClickListener() { 550 | public void onClick(final DialogInterface dialog, final int w) { 551 | finish(); 552 | } 553 | }) 554 | .setNegativeButton(R.string.no, null) 555 | .create(); 556 | } 557 | 558 | return super.onCreateDialog(id); 559 | } 560 | } 561 | --------------------------------------------------------------------------------