├── .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 |
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 |
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 |
--------------------------------------------------------------------------------